export const questions: { [key: string]: { q: string; a: string }[] } = {
    js: [
        {
            q: '什么是js的原型链？',
            a: 'JavaScript的原型链是指当访问一个对象的属性或方法时，如果该对象本身没有定义该属性或方法，JavaScript引擎会在该对象的原型对象上进行查找，如果仍然未找到，则继续访问原型对象的原型对象，直到最后一个原型对象为止。这种原型对象之间形成的链状结构就是原型链。',
        },
        {
            q: '什么是js的闭包？',
            a: 'JavaScript闭包是指可以访问其外部函数作用域中变量的内部函数，即使外部函数已经执行完毕并返回，闭包仍然可以访问和操作这些变量。它可以用来创建私有变量和方法并实现高阶函数等功能。',
        },
        {
            q: `js的垃圾回收机制？`,
            a: 'JavaScript使用垃圾回收机制来自动管理内存。具体来说，JavaScript中的垃圾回收器会定期检查不再被引用的对象，并将它们标记为垃圾并清除它们的内存空间，以便可以重新使用该空间。\n\nJavaScript使用的主要垃圾回收算法是基于“标记-清除”（Mark and Sweep）的。它首先标记不再被引用的对象，然后清除这些对象占用的内存空间。\n\n内除了自动垃圾回收机制之外，JavaScript还提供了手动管理内存的功能，例如可以使用 delete 操作符删除单个变量或属性，并释放它们占用的内存空间。',
        },
        {
            q: 'ES6新特性？',
            a: 'ES6是ECMAScript 2015标准的缩写，它引入了许多新的语言特性，其中一些包括：\n\n1. 块级作用域变量声明（let、const）\n\n2. 箭头函数表达式\n\n3. 默认参数值\n\n4. 模板字面量\n\n5. 解构赋值\n\n6. for...of循环\n\n7. 类和继承\n\n8. Promise异步编程\n\n9. 模块导入/导出',
        },
        {
            q: 'this指向问题？',
            a: '在 JavaScript 中，关键字 `this` 可以用于引用当前代码执行的上下文对象。`this` 的指向是在运行时根据调用方式动态确定的，具体取决于函数的调用方式。\n\n如果函数作为普通函数直接被调用，那么 `this` 指向全局对象 `window`（或 `global` 对象，在 Node.js 环境中）。如果函数被用作方法调用，例如一个对象的方法，`this` 将引用调用该方法的对象。在使用 `new` 关键字创建构造函数实例时，`this` 将指向新创建的实例对象。\n\n此外，也可以使用 `bind`, `call`, 或 `apply` 方法改变函数内部 `this` 的指向。\n\n需要注意的是，在箭头函数中，`this` 是词法绑定的，即与函数定义时的上下文环境保持一致，而不是根据调用方式动态变化的。',
        },
        {
            q: '防抖节流函数及其作用场景？',
            a: '防抖和节流是两种常用的函数优化技术，可用于减少浏览器事件的处理次数，提高页面性能。\n\n防抖函数（Debounce）是指在事件触发 n 秒后再执行回调函数，若在这 n 秒内又有相同事件触发，则重新计时，只会执行最后一次事件。适用于用户频繁操作、如搜索框输入联想等场景。\n\n节流函数（Throttle）是指每隔一段时间执行一次回调函数，也就是将高频率时间降至低频率时间，适用于如页面滚动、鼠标移动等场景。',
        },
        {
            q: 'Set和Map的区别？',
            a: 'JavaScript中的Set和Map都是用来存储数据集合的数据结构，但它们之间有一些区别：\n\n1. Set是一组唯一值的集合，可以包含任何类型的值；Map是一组键值对的集合，其中每个键都唯一对应一个值。\n\n2. Set使用add()方法添加新的元素，而Map使用set()方法向其中添加新的键值对。\n\n3. Set可以轻松地找到某个特定值是否在集合中，而Map可以快速查找键对应的值。\n\n4. Set没有keys()、values()和entries()三个方法，因为在Set中，键和值是相同的；而Map具有这些方法，可以用于迭代键、值或键值对。\n\n5. 在迭代时，Set返回插入顺序的值，而Map默认情况下返回插入顺序的键值对。\n\n总之，Set适用于存储唯一值的情况，而Map适用于需要以一种高效方式存储键值对的情况。',
        },
        {
            q: 'let和const，var之间的区别？',
            a: '在JavaScript中，`let`和`const`声明的变量与`var`声明的变量有以下不同：\n\n1. `var`声明的变量的范围是函数级别的，而`let`和`const`声明的变量的范围是块级别的（例如，花括号内）。\n\n2. 在同一作用域内，`var`可以被重复声明，但是`let`和`const`不允许。\n\n3. 由于作用域的不同，`let`和`const`声明的变量也具有更好的暂时性死区（TDZ）特性。如果在声明之前访问它们，将会抛出一个引用错误，而`var`没有TDZ特性。\n\n4. `let`声明的变量可以被重新赋值，但`const`声明的变量不能。\n\n所以，`let`和`const`通常被认为是更可靠和可预测的变量声明方式，推荐使用它们来取代`var`。',
        },
        {
            q: 'js五种继承方式?',
            a: 'JavaScript中的五种继承方式包括：\n\n1. 原型链继承\n\n2. 构造函数继承\n\n3. 组合继承\n\n4. 原型式继承\n\n5. 寄生式继承\n\n它们各自的优缺点如下：\n\n1. 原型链继承：\n\n  - 优点：简单易用，可实现基本继承。\n\n  - 缺点：所有实例共享原型对象中的属性和方法，并且无法向父类传递参数，容易导致子类型的实例数据被修改。\n\n2. 构造函数继承：\n\n  - 优点：可以向父类构造函数传递参数，避免了原型链继承的属性共享问题。\n\n  - 缺点：子类型无法访问父类型原型对象上的属性和方法，每个实例都会创建一份方法的副本。\n\n3. 组合继承：\n\n  - 优点：结合了原型链继承和构造函数继承的优点，既能实现函数复用，又能保证实例之间的独立性。\n\n  - 缺点：组合继承有一个缺陷就是无论什么情况下，都会调用两次超类型的构造函数，第一次是在创建子类型的时候，第二次是在子类型构造函数内部。\n\n4. 原型式继承：\n\n  - 优点：可以简单地复制一个对象，用它作为另一个对象的原型对象。\n\n  - 缺点：如果原型对象中某个引用类型值被修改，则被所有实例共享。\n\n5. 寄生式继承：\n\n  - 优点：在不修改原对象的基础上，为其添加新的属性和方法。\n\n  - 缺点：仍然会存在引用类型的问题。\n\n此外，还有一个class类继承：ES6的类通过extends来实现继承，通过super来调用父类的构造方法（相当于ES5的构造函数继承）',
        },
        {
            q: '上传和下载文件的几种方式？',
            a: 'JavaScript 上传文件的方式：\n\n1. 使用表单提交（form）：通过创建一个带有“file”类型输入框的HTML表单，用户可以从本地选择要上传的文件并将其提交到服务器。\n\n2. 使用XMLHttpRequest对象：可以通过JavaScript代码创建XMLHttpRequest对象并使用它来上传文件。这种方式需要将文件转换为二进制格式。\n\n3. 使用Fetch API：Fetch API是JavaScript的一种新技术，可以用于上传文件。这种方式同样需要将文件转换成二进制格式。\n\nJavaScript 下载文件的方式：\n\n1. 使用链接<a>标签：在HTML中创建一个包含文件地址的链接，用户单击链接即可下载文件。\n\n2. 使用XMLHttpRequest对象：可以通过JavaScript代码创建XMLHttpRequest对象并使用它来下载文件。可以使用responseType属性将响应类型设置为“blob”，从而获取一个二进制文件。\n\n3. 使用Fetch API：Fetch API也可以用于下载文件。类似于XMLHttpRequest，可以使用responseType属性将响应类型设置为“blob”。',
        },
        {
            q: 'new操作符的过程？',
            a: '使用 "new" 操作符创建对象的过程如下：\n\n1. 创建一个空对象。\n\n2. 设置对象的原型链，使其指向构造函数的原型对象。\n\n3. 将构造函数中的 "this" 指向新对象，并执行构造函数。这样，构造函数中的属性和方法就可以被添加到新对象上。\n\n4. 如果构造函数没有明确返回一个对象，则返回该新对象。\n\n例如，假设有以下构造函数：\n\n```\n\nfunction Person(name, age) {\n\n  this.name = name;\n\n  this.age = age;\n\n}\n\n```\n\n使用 "new" 操作符创建一个 Person 对象的过程如下：\n\n```\n\nvar person1 = new Person("John", 30);\n\n//会执行以下流程：\n\n// 1. 创建一个空对象。\n\n// 2. 将对象的原型链指向 Person.prototype。\n\n// 3. 将构造函数中的 "this" 指向新对象，并执行构造函数。\n\n//    此时，person1 对象的 name 属性和 age 属性被设置为 "John" 和 30，分别。\n\n// 4. 返回这个新对象，即 person1。\n\n```',
        },
        {
            q: 'http和https的区别？',
            a: 'HTTP和HTTPS的最大区别在于安全性。HTTP是明文传输，数据不加密，因此容易被黑客窃取或篡改；而HTTPS采用了SSL/TLS协议进行加密传输，能够保证数据的机密性、完整性和真实性，更加安全可靠。另外，HTTPS的URL会以"https://"开头，而HTTP则是"http://". 这是它们的最显著区别，但还有其他细微差别，例如默认端口号等等。',
        },
        {
            q: '各种循环及其使用场景？',
            a: 'Javascript 中有四种主要的循环：\n\n1. `for` 循环：用来重复执行一段代码，可以指定循环变量的初始值、结束条件和每次迭代后变化的方式。\n\n```javascript\nfor (let i = 0; i < 10; i++) {\n  console.log(i);\n}\n```\n2. `while` 循环：当条件为真时，执行一段代码块，直到条件为假或者循环被打断。\n```javascript\nlet i = 0;\nwhile (i < 10) {\n  console.log(i);\n  i++;\n}\n```\n3. `do...while` 循环：先执行一次循环体，然后检查退出条件。如果满足，则继续执行循环，否则退出循环。\n```javascript\nlet i = 0;\ndo {\n  console.log(i);\n  i++;\n} while (i < 10);\n```\n4. `for...in` 循环：用于遍历对象的属性名，将变量绑定到每个可枚举的属性上，但不包括对象原型链上的属性。\n```javascript\nconst obj = {a: 1, b: 2, c: 3};\nfor (let prop in obj) {\n  console.log(`${prop}: ${obj[prop]}`);\n}\n```\n\n这些循环可以根据使用场景进行选择。通常，在需要事先知道循环次数时，使用 `for` 循环；当循环次数不确定时，使用 `while` 或 `do...while` 循环；当需要遍历对象属性时，使用 `for...in` 循环。',
        },
        {
            q: 'Promise解决了什么问题？',
            a: 'JavaScript Promise解决了JavaScript异步编程的一些问题。在过去，JavaScript中使用回调函数来处理异步代码，这会导致“回调地狱”——深度嵌套和难以阅读的代码。而Promise通过提供一种简洁和可读性更强的语法来处理异步操作，解决了这个问题。Promise使得我们能够通过链式语法来处理异步代码，避免嵌套并且易于理解和维护。此外，Promise还具有容错处理和错误处理机制，使得异步代码的调试和维护变得更加容易。',
        },
        {
            q: '事件流与事件委托？',
            a: 'JavaScript中的事件流描述了当一个事件被触发时它在DOM树上的传播过程。事件委托是一种利用事件流的技术，通过将事件处理程序附加到父元素而不是每个子元素来提高性能和代码效率。\n\n当一个事件在DOM树上触发时，该事件会沿着两种不同的方向传播：捕获阶段和冒泡阶段。捕获阶段是自顶向下的，而冒泡阶段则是自底向上的。事件委托利用了事件冒泡的特性，可以将处理程序附加到祖先元素，这样当一个子元素引起事件时，该事件会冒泡到其祖先元素，并且祖先元素上的事件处理程序将被调用。\n\n通过使用事件委托，我们可以节省时间和内存，因为只需要一个事件处理程序即可处理许多子元素的事件，而不需要为每个子元素分别添加处理程序。\n\n优点：\n\n1. 减少事件处理程序的数量，提高性能和内存利用率。\n\n2. 动态添加或删除子元素时，无需重新绑定事件处理程序，节省代码量和维护成本。\n\n3. 可以处理大量具有相似交互的子元素，例如表格中多行的点击行为。\n\n缺点：\n\n1. 如果遇到复杂的事件处理逻辑（如 drag & drop），可能会使代码难以理解和维护。\n\n2. 容易产生事件冒泡（bubbling）或事件捕获（capturing）的问题，需要仔细管理事件流程。\n\n3. 事件委托仅适用于某些特定情况下，若需对子元素的事件有复杂的操作，反而使用普通方法可能更好。',
        },
        {
            q: 'js数据类型？',
            a: 'JavaScript中的数据类型包括原始类型和对象类型。\n\n原始类型包括：\n\n- 数字（Number）\n\n- 字符串（String）\n\n- 布尔值（Boolean）\n\n- 空值（null）\n\n- 未定义（undefined）\n\n- 符号（Symbol，ES6新增）\n\n对象类型包括：\n\n- 对象（Object）\n\n- 数组（Array）\n\n- 函数（Function）\n\n- 正则表达式（RegExp）\n\n- Date对象\n\n- Math对象\n\nJavaScript还有两种特殊的数据类型：\n\n- NaN：代表“不是一个数字”的值\n\n- Infinity：代表正无穷大',
        },
        {
            q: 'null和undefined的区别？',
            a: '在JavaScript中，null和undefined都表示无值的状态。\n\nnull表示一个被明确赋值为空对象的变量。\n\nundefined 会出现在访问对象未定义的属性或函数未定义的参数时，也可以作为变量赋值为 undefined。\n\n通常我们使用 null 来显式地指示“无值”，而使用 undefined 表示“未定义”或“缺失的值”。',
        },
        {
            q: 'get和post的区别？',
            a: 'GET和POST都是HTTP协议中常用的请求方法，二者的主要区别包括:\n\n1. GET是从服务器上获取资源，而POST是向服务器提交数据。\n\n2. 在URL中，GET请求会将参数附加在URL后面，并使用问号（?）和&符号将它们连接在一起，而POST请求则将数据放在请求正文中。\n\n3. GET请求的数据量通常比POST请求少，因为GET请求有URL长度限制，而POST请求没有这个限制。\n\n4. GET请求可以被浏览器缓存，因此可以更快地访问，而POST请求不会被缓存。\n\n5. GET请求参数可以被搜索引擎索引，POST请求参数不会被搜索引擎索引。\n\n6. GET请求对于不能修改服务器数据的请求是很适合的，如获取资源，查询等，而POST请求对于需要修改服务器数据的请求是很适合的，如添加或更新资源。',
        },
        {
            q: '处理跨域的方法？',
            a: '在JS中处理跨域有以下几种方法：\n\n1. JSONP：通过动态创建script标签，向其他域名的服务器请求数据并利用回调函数返回结果。\n\n2. CORS：服务端设置Access-Control-Allow-Origin响应头，允许某些或所有来源与该服务器进行跨域通信。\n\n3. 代理：在同源策略下，通过同一个服务器请求不同域名的接口，再将数据返回给前端。\n\n4. WebSocket：使用WebSocket协议实现跨域通信。\n\n5. postMessage：在不同窗口间（或者不同iframe或者web worker等）传递数据，为了防止document.domain被篡改，可以在两者的文档片段中分别设置相同的安全域名 即document.domain=example.com;\n\n需要注意的是，在使用以上方法时，要考虑到安全性和可靠性等方面的问题。',
        },
        {
            q: '谈谈 script标签中 defer和 async属性的区别',
            a: 'defer和async属性都是用于异步加载外部的JS脚本文件，它们都不会阻塞页面的解析，从而可以并行加载JS文件。然而，它们之间在执行脚本的时机和顺序上存在一些区别。\n\n执行时机：defer属性的脚本会在页面加载结束后再执行，具体来说是在DOMContentLoaded事件触发之前，但在window.onload事件触发之后。而async属性的脚本会在脚本加载完成后立即执行，这可能会导致在页面尚未完全解析的情况下就执行了脚本。\n\n执行顺序：如果页面中存在多个带有async属性的脚本，那么这些脚本的执行顺序是无法保证的，它们会按照加载完成的顺序执行，谁先加载完成就先执行谁。而对于带有defer属性的脚本，它们会按照在页面中出现的顺序执行，即使后面的脚本先加载完成，也会等待前面的脚本执行完毕后再执行。\n\n因此，在选择使用defer还是async属性时，需要根据具体的场景和需求来决定。如果你希望脚本按照特定的顺序执行，并且不希望在页面尚未完全解析的情况下就执行脚本，那么可以选择使用defer属性。而如果你不关心脚本的执行顺序，只希望尽快地加载并执行脚本，那么可以选择使用async属性。                \n\n另外需要注意的是，当defer和async属性同时存在时，async的优先级更高，即脚本会按照async属性的方式加载和执行。但是这种做法并不推荐，因为这可能会导致混淆和不可预测的行为。最好的做法是只选择其中一个属性来使用。\n\n此外，还需要注意的是，defer和async属性只对外部脚本有效，对于内联脚本（即直接在<script>标签中编写JS代码）是无效的。同时，使用这两个属性的脚本中不能调用document.write方法，因为这会阻塞页面的解析和渲染。',
        },
        {
            q: 'JavaScript中encodeURI和decodeURI',
            a: '在JavaScript中，encodeURI和decodeURI是内建的全局函数，它们分别用于对统一资源标识符（URI）进行编码和解码。这些函数在处理Web地址（URLs）和URI时特别有用，因为它们能够确保字符串在作为URL的一部分进行传输时格式正确且不会引发错误。\n\nencodeURI\nencodeURI函数接受一个字符串作为参数，并返回一个经过编码的新字符串。在编码过程中，所有非字母数字的字符都将被替换为它们对应的UTF-8编码形式的百分比编码（例如 %XX，其中 XX 是两位的十六进制数）。但是，该函数不会对URI中具有特殊含义的字符进行编码，如冒号（:）、正斜杠（/）、问号（?）和井号（#）。\n\njavascript\nvar uri = "http://example.com/some space";  \nvar encodedURI = encodeURI(uri);  \nconsole.log(encodedURI); // 输出: http://example.com/some%20space\n在上面的例子中，空格被编码成了%20。\n\ndecodeURI\ndecodeURI函数则是encodeURI的逆操作。它接受一个经过编码的URI字符串作为参数，并尝试将其解码回原始形式。解码过程中，百分比编码的字符会被替换回它们对应的字符。\n\njavascript\nvar encodedURI = "http://example.com/some%20space";  \nvar decodedURI = decodeURI(encodedURI);  \nconsole.log(decodedURI); // 输出: http://example.com/some space\n在上面的例子中，%20被解码回了空格。\n\n注意事项\n如果你需要编码URI的某个组成部分（如查询参数），而不仅仅是整个URI，那么可能更适合使用encodeURIComponent和decodeURIComponent函数，因为它们会对所有非字母数字的字符进行编码，包括那些在URI中具有特殊含义的字符。\n使用encodeURI和decodeURI时，请确保你了解哪些字符会被编码，哪些字符会保留原样，以避免潜在的安全问题或编码错误。',
        },
        {
            q: '为什么不建议在 JavaScript中使用 innerHTML？',
            a: '在JavaScript中使用innerHTML有一些潜在的问题，这也是为什么有些开发者不建议使用它的原因：\n\nXSS（跨站脚本攻击）：innerHTML会解析传入的HTML字符串，并作为实际的HTML内容插入到元素中。这意味着如果字符串中包含<script>标签或其他可执行的JavaScript代码，那么这些代码将被执行。这就为XSS攻击提供了可能，攻击者可以注入恶意脚本，窃取用户的敏感信息，或者进行其他恶意行为。\n\n性能问题：频繁地使用innerHTML可能会导致性能问题，因为每次设置innerHTML时，浏览器都需要解析并重新构建DOM。这在处理大量数据时可能会导致性能瓶颈。\n\n破坏元素状态和数据：使用innerHTML替换元素的内容时，会删除所有子节点及其关联的事件监听器和数据。如果你只是想修改元素的一部分内容，或者添加新的子节点，那么使用innerHTML可能会导致不必要的工作和潜在的问题。\n\n尽管有这些潜在的问题，但在某些情况下，使用innerHTML可能是最简单和最直接的方法。然而，你应该始终谨慎地使用它，并确保传入的HTML字符串是安全的。你可以使用各种方法来清理和转义HTML字符串，以防止XSS攻击。另外，你也可以考虑使用其他方法来修改DOM，如textContent（只设置文本内容，不解析HTML标签），createElement和appendChild（创建和添加新的DOM元素）等。',
        },
        {
            q: 'JavaScript延迟加载的方式有哪些？',
            a: '在Web开发中，延迟加载（Lazy Loading）是一种常用的优化技术，它允许我们推迟加载某些非关键资源，直到它们真正需要的时候。对于JavaScript来说，延迟加载可以帮助我们改善页面加载性能，减少初始加载时间，并降低带宽消耗。以下是几种常见的JavaScript延迟加载方式：\n\n将脚本放在文档的底部：\n将<script>标签放在HTML文档的底部（通常是</body>标签之前）是一种简单的延迟加载方法。这样做可以确保在解析和执行JavaScript代码之前，页面的其余部分已经被加载和渲染。\n\n使用async属性：\n在<script>标签中添加async属性可以让浏览器异步加载和执行脚本，这样就不会阻塞页面的渲染。但请注意，async脚本的执行顺序是不确定的。\n\nhtml\n<script async src="your-script.js"></script>\n使用defer属性：\ndefer属性告诉浏览器要等到整个HTML文档解析完成之后，才会执行脚本。与async不同，defer脚本会按照它们在文档中出现的顺序执行。\n\nhtml\n<script defer src="your-script.js"></script>\n动态创建脚本元素：\n你可以使用JavaScript动态地创建<script>元素，并设置其src属性来加载脚本。这种方法允许你根据需要延迟加载脚本，甚至可以在某些条件下决定是否加载。\n\njavascript\nconst script = document.createElement(\'script\');  \nscript.src = \'your-script.js\';  \ndocument.body.appendChild(script);\n使用事件监听器进行条件加载：\n你可以监听某些事件（如滚动、点击等），当这些事件发生时再加载相应的脚本。例如，你可以使用Intersection Observer API来实现图片或特定元素的懒加载。\n\n使用模块加载器或打包工具：\n使用如Webpack、Rollup等模块打包工具可以将你的代码分割成多个块（chunk），并实现按需加载。这些工具通常提供了动态导入（dynamic imports）的功能，允许你在运行时加载JavaScript模块。\n\n使用requestAnimationFrame进行延迟：\nrequestAnimationFrame函数可以让你在下一次重绘之前调用指定的函数更新动画。虽然它主要用于动画，但也可以作为一种延迟执行代码的手段。不过，这并不是一种标准的延迟加载脚本的方法，但在某些特定场景下可能有用。\n\n使用第三方库：\n还有一些第三方库和框架提供了更高级的延迟加载功能，如lozad.js用于图片的延迟加载，或者像React、Vue等前端框架中的代码分割和懒加载机制。\n\n选择哪种延迟加载方法取决于你的具体需求和项目上下文。在决定如何实施延迟加载时，请考虑用户体验、性能以及代码的复杂性和可维护性。',
        },
        {
            q: 'js中 call()和apply()的区别和作用是什么？',
            a: '在JavaScript中，call()和apply()都是函数对象的方法，它们的主要作用是改变函数的执行上下文（也就是函数内部的this值）。具体来说，call()和apply()方法都可以调用一个具有给定this值的函数，以及作为一个数组（或类似数组对象）提供的参数。\n\ncall()和apply()的区别主要在于它们接受参数的方式：\n\ncall()方法接受的是参数列表，即你可以将函数的参数逐个传入call()方法中。例如：func.call(thisArg, arg1, arg2, ...)。\napply()方法接受的是一个参数数组，即你需要将函数的参数以一个数组的形式传入apply()方法中。例如：func.apply(thisArg, [argsArray])。\n这两个方法的作用主要有以下几点：\n\n调用函数：call()和apply()都可以用来调用函数，即使这个函数是在别的对象上定义的。\n改变this的指向：call()和apply()的第一个参数都是用来指定函数运行时的this值的。如果这个参数是null或undefined，那么this会被视为全局对象。如果是原始值（number、boolean、string），那么this的值将是这些原始值的包装对象。\n借用方法：由于call()和apply()可以改变this的指向，所以我们可以通过这两个方法来借用其他对象的方法。这在某些情况下非常有用，例如我们可以借用数组对象的内置方法来处理类数组对象。\n注意，虽然call()和apply()非常强大，但如果不当使用可能会导致代码难以理解和维护。因此，在使用这两个方法时，一定要谨慎考虑它们的影响和副作用。',
        },
        {
            q: 'js中哪些操作会造成内存泄漏？',
            a: '在JavaScript中，内存泄漏主要是由于未正确管理内存而导致的，这通常涉及到不再需要的对象仍然被引用，因此垃圾回收器无法释放它们。以下是一些常见的JavaScript内存泄漏的情况：\n\n全局变量：在JavaScript中，全局变量的生命周期直到页面关闭才结束。如果你不小心创建了全局变量并存储了大量数据，这些数据只有在页面关闭时才会被释放，这可能导致内存泄漏。\n\n闭包：闭包可以维持函数内部变量的引用，但如果使用不当，它们可能导致内存泄漏。特别是当闭包引用了外部大对象或DOM元素时，如果这些闭包长时间存在或大量创建，可能会导致内存占用持续增长。\n\n定时器：使用setInterval或setTimeout创建的定时器如果不被清除，会一直存在并执行回调函数，即使你可能不再需要它们。如果回调函数引用了外部变量或DOM元素，这些资源也不会被释放。\n\nDOM引用：当DOM元素被删除或替换时，如果仍有JavaScript对象引用这些元素，那么这些DOM元素就不会被垃圾回收器回收，从而导致内存泄漏。常见的场景包括事件监听器未被移除或存储在JavaScript对象中的DOM引用未被清除。\n\n循环引用：当两个或更多对象相互引用，形成一个无法到达外部引用的闭环时，如果闭环中的任何对象都无法到达根（通常是全局对象），则这些对象都应该被垃圾回收。然而，在某些旧版本的浏览器或JavaScript引擎中，垃圾回收器可能无法处理这种情况，导致内存泄漏。现代浏览器通常能够检测并处理这种循环引用的情况。\n\n大数组和对象：大数组或对象（特别是嵌套深的对象）会占用大量内存。如果这些数据结构不再需要但仍被引用，它们就会占用宝贵的内存资源。\n\nWeb Workers：Web Workers允许在后台线程中运行JavaScript代码，不会阻塞主线程。但是，如果Web Worker没有被正确终止，它可能会继续运行并消耗内存。\n\n缓存：不正确地使用缓存机制（如将大量数据存储在localStorage或sessionStorage中）也可能导致内存问题，尽管这些数据不会直接占用JavaScript堆内存，但它们会影响浏览器的整体内存使用。\n\n为了避免内存泄漏，开发者应该遵循良好的编程实践，包括及时清除不再需要的引用、使用局部变量而非全局变量、在适当的时候注销事件监听器和清除定时器、以及谨慎使用闭包和缓存机制。此外，使用浏览器的开发者工具（如Chrome的DevTools）来监控内存使用情况并检测潜在的内存泄漏也是非常重要的。',
        },
        {
            q: 'js如何实现异步编程？',
            a: "在JavaScript中，异步编程主要可以通过以下几种方式实现：\n\n回调函数（Callback Functions）：这是JavaScript异步编程的基础。许多内置的JavaScript函数都接受回调函数作为参数，这些回调函数在异步操作完成后被调用。例如，setTimeout和AJAX请求都使用回调函数来处理异步操作。但是，当异步操作较多时，可能会出现所谓的\"回调金字塔\"，使得代码难以阅读和维护。\nPromise：Promise是一种更先进的异步编程解决方案，它代表了异步操作的最终完成或失败。Promise有三种状态：pending（等待中）、fulfilled（已成功）和rejected（已失败）。一旦Promise的状态从pending变为fulfilled或rejected，就不会再改变。Promise可以链式调用，避免了回调金字塔的问题。\n下面是一个Promise的例子：\n\njavascript\nlet promise = new Promise((resolve, reject) => {  \n    // 异步操作  \n    setTimeout(() => {  \n        resolve('成功的结果');  // 当异步操作成功时，调用resolve()函数，并将结果作为参数传入  \n    }, 1000);  \n});  \n  \npromise.then(result => {  \n    console.log(result);  // 输出：'成功的结果'  \n}).catch(error => {  \n    console.log(error);  // 如果异步操作失败，会在这里捕获到错误  \n});\nasync/await：async/await是基于Promise实现的，它使得异步代码的书写和理解更像同步代码。async关键字用于声明一个函数是异步的，而await关键字则用于等待一个Promise完成并返回其结果。注意，await只能在async函数中使用。\n下面是一个async/await的例子：\n\njavascript\nasync function asyncFunc() {  \n    return new Promise((resolve, reject) => {  \n        setTimeout(() => {  \n            resolve('成功的结果');  \n        }, 1000);  \n    });  \n}  \n  \nasync function main() {  \n    try {  \n        let result = await asyncFunc();  // 等待asyncFunc()函数的结果  \n        console.log(result);  // 输出：'成功的结果'  \n    } catch (error) {  \n        console.log(error);  // 如果异步操作失败，会在这里捕获到错误  \n    }  \n}  \n  \nmain();\n以上就是JavaScript实现异步编程的三种主要方式。其中，async/await是最新的方式，也是目前最推荐使用的方式，因为它使得异步代码的书写和理解更加直观和简洁。",
        },
        {
            q: '请解释一下 JavaScript的同源策略。',
            a: 'JavaScript的同源策略（Same-Origin Policy）是一种重要的安全机制，用于防止来自不同源的脚本对当前页面的数据进行未经授权的访问和操作。这个策略是Web应用程序安全模型的重要组成部分，它限制了从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。\n\n在JavaScript中，“同源”指的是两个页面具有相同的协议（例如HTTP或HTTPS）、主机名和端口号。只有当这些条件都满足时，才能认为两个页面是同源的。如果其中任何一个条件不满足，则被视为不同源。\n\n同源策略主要限制了以下几个方面：\n\nDOM访问：不同源的脚本不能访问和操作当前页面的DOM元素。这防止了恶意脚本对其他页面的敏感数据进行窃取或篡改。\n数据存储：不同源的脚本不能共享相同的cookie、localStorage或sessionStorage等数据存储机制。这有助于保护用户的隐私和会话安全。\nAJAX请求：不同源的脚本不能通过AJAX请求获取其他源的数据。这防止了跨站请求伪造（CSRF）等攻击手段。\n然而，同源策略也带来了一些限制和不便之处。例如，有时我们需要在不同源的页面之间进行数据共享或通信。为了解决这个问题，浏览器提供了一些跨源资源共享（CORS）的机制，允许在特定条件下进行跨源请求和数据交换。此外，还有一些技术（如JSONP、postMessage等）可以用于实现跨源通信。\n\n需要注意的是，虽然同源策略是一种重要的安全机制，但它并不能完全防止所有的安全漏洞和攻击。因此，在开发Web应用程序时，还需要采取其他安全措施来保护用户的数据和隐私。',
        },
        {
            q: '在 JavaScript中，为什么说函数是第一类对象？',
            a: '在JavaScript中，函数被称为第一类对象（First-class objects），这是因为它们具有对象的大部分特性，并且可以在程序运行时被动态地创建、使用和销毁。以下是一些证明函数是第一类对象的特性：\n\n它们可以被赋值给变量：在JavaScript中，函数可以被赋值给变量，就像其他类型的对象一样。例如：\n\njavascript\nlet myFunction = function() {  \n    console.log("Hello, world!");  \n};\n它们可以作为参数传递给其他函数：函数可以作为参数传递给其他函数，这种行为被称为高阶函数（Higher-order functions）。例如：\n\njavascript\nfunction callFunctionWithParameter(fn, param) {  \n    fn(param);  \n}  \n  \nfunction sayHello(name) {  \n    console.log(\'Hello,\', name, \'!\');  \n}  \n  \ncallFunctionWithParameter(sayHello, "Alice");\n它们可以作为其他函数的返回值：函数可以作为其他函数的返回值，使得我们可以创建和返回新的函数。例如：\n\njavascript\nfunction createGreetingFunction(name) {  \n    return function() {  \n        console.log(\'Hello,\', name, \'!\'); \n    };  \n}  \n  \nlet greetAlice = createGreetingFunction("Alice");  \ngreetAlice();\n它们可以拥有属性和方法：就像其他对象一样，函数也可以有自己的属性和方法。例如，所有的函数都有一个length属性，该属性表示函数的参数数量。此外，函数也可以被赋予自定义的属性。\n\njavascript\nfunction myFunction() {  \n    console.log("Hello, world!");  \n}  \n  \nmyFunction.customProperty = "Hello, custom world!";  \nconsole.log(myFunction.customProperty);  // 输出："Hello, custom world!"\n因此，由于这些原因，JavaScript中的函数被视为第一类对象。',
        },
        {
            q: '函数声明与函数表达式的区别？',
            a: '函数声明（Function Declaration）和函数表达式（Function Expression）在JavaScript中都是定义函数的方式，但它们之间有一些关键的区别。\n\n函数声明（Function Declaration）：\n\n函数声明会进行函数提升（Hoisting），即在执行代码之前，函数声明会被提前到所在作用域（Scope）的顶部。这意味着，无论函数声明在代码中的位置如何，都可以在声明之前的代码中调用该函数。\n函数声明必须具有函数名称，也就是说，它们必须被命名。\n函数声明是独立的语句，不能作为其他语句的一部分（例如，不能作为赋值语句的右侧部分）。\n示例：\n\njavascript\nfunction myFunction() {  \n    console.log("This is a function declaration.");  \n}  \n  \nmyFunction(); // 输出："This is a function declaration."\n函数表达式（Function Expression）：\n\n函数表达式不会像函数声明那样进行函数提升。它们只在执行到达表达式所在的代码行时才会被创建。因此，在函数表达式之前的代码中无法调用该函数。\n函数表达式可以是匿名的（没有名称），也可以被命名。但是，当函数表达式被赋值给一个变量时，通常使用匿名函数。命名函数表达式主要用于调试和递归调用。\n函数表达式可以作为其他语句的一部分，例如赋值语句的右侧部分、作为回调函数传递给其他函数等。\n示例：\n\njavascript\nconst myFunctionExpression = function() {  \n    console.log("This is a function expression.");  \n};  \n  \nmyFunctionExpression(); // 输出："This is a function expression."\n总结：\n\n函数声明会进行函数提升，而函数表达式不会。\n函数声明必须具有名称，而函数表达式可以是匿名的或命名的。\n函数声明是独立的语句，而函数表达式可以作为其他语句的一部分。',
        },
        {
            q: 'js如何对cookie进行增删改和设置过期时间',
            a: '在JavaScript中，对cookie进行操作（包括增加、删除、修改和设置过期时间）主要通过document.cookie属性来实现。下面是一些基本的操作示例：\n\n设置cookie：\n使用document.cookie可以设置新的cookie。默认情况下，cookie的有效期只会在当前浏览器会话中存在，当浏览器关闭时cookie会被删除。要设置过期时间，需要使用expires属性。\n\njavascript\nfunction setCookie(name, value, days) {  \n    var expires = "";  \n    if (days) {  \n        var date = new Date();  \n        date.setTime(date.getTime() + (days*24*60*60*1000));  \n        expires = "; expires=" + date.toUTCString();  \n    }  \n    document.cookie = name + "=" + (value || "")  + expires + "; path=/";  \n}\n在上述函数中，days参数表示cookie的存活天数。如果不设置days，则cookie只在当前会话中有效。\n\n获取cookie：\n\njavascript\nfunction getCookie(name) {  \n    var nameEQ = name + "=";  \n    var ca = document.cookie.split(\';\');  \n    for(var i=0;i < ca.length;i++) {  \n        var c = ca[i];  \n        while (c.charAt(0)==\' \') c = c.substring(1,c.length);  \n        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);  \n    }  \n    return null;  \n}\n删除cookie：\n删除cookie的一种常见方法是将过期时间设置为一个已经过去的时间。\n\njavascript\nfunction eraseCookie(name) {     \n    document.cookie = name+\'=; Max-Age=-99999999;\';    \n}\n或者，你也可以通过设置expires为一个过去的时间来删除cookie：\n\njavascript\nfunction eraseCookie(name) {  \n    createCookie(name,"",-1);  \n}\n这里的createCookie函数和前面的setCookie函数类似，只是名称不同。你需要确保createCookie函数能够处理负数的days参数，并将其转换为过去的日期。\n\n但是，更推荐的做法是直接使用setCookie函数并传入一个负数作为过期天数，如下所示：\n\njavascript\nfunction eraseCookie(name) {  \n    setCookie(name, "", -1);  \n}\n然后，在setCookie函数内部处理负数的逻辑：\n\njavascript\nfunction setCookie(name, value, days) {  \n    var expires = "";  \n    if (days) {  \n        var date = new Date();  \n        date.setTime(date.getTime() + (days*24*60*60*1000));  \n        expires = "; expires=" + date.toUTCString();  \n    } else if (days === -1) {  \n        // 当days为-1时，删除cookie  \n        expires = "; expires=Thu, 01 Jan 1970 00:00:00 UTC";  \n    }  \n    document.cookie = name + "=" + (value || "")  + expires + "; path=/";  \n}\n修改cookie：\n实际上，JavaScript并没有直接修改cookie的方法。要修改一个cookie，你通常需要先删除旧的cookie，然后设置一个新的cookie来代替它。这可以通过结合使用删除和设置cookie的函数来实现。例如：\n\njavascript\neraseCookie(\'myCookie\'); // 删除旧的cookie  \nsetCookie(\'myCookie\', \'newValue\', 30); // 设置新的cookie，有效期为30天\n',
        },
        {
            q: 'html中对于元素， attribute和 property的区别是什么？',
            a: '在HTML中，元素、属性和特性（通常被称为属性（attribute）和属性（property）在JavaScript中）之间有着明显的区别。以下是这三者之间的主要差异：\n\nHTML元素：HTML元素是构成HTML页面的基础。例如，<div>、<p>、<a>等都是HTML元素。元素通常由开始标签、内容和结束标签组成（尽管有些元素可能是自闭合的，如<img>或<br>）。\nHTML属性（Attribute）：HTML属性提供有关元素的额外信息，这些信息通常不会由元素的内容直接显示。它们总是包含在元素的开始标签中，并且总是以名称/值对的形式出现，比如：<a href="https://www.example.com">链接</a>。在这个例子中，href就是一个属性，它的值是"https://www.example.com"。注意，属性的值总是被引号包围，尽管在某些情况下，HTML规范可能允许省略引号。\nJavaScript属性（Property）：在JavaScript中，HTML元素被表示为对象，而这些对象具有属性和方法。属性（property）是这些对象的状态或特性，例如一个元素的id、className或innerHTML。与HTML属性不同，JavaScript属性可以直接通过点记法（.）或方括号记法（[]）在元素对象上进行访问和修改。例如，如果有一个id为"myElement"的元素，那么可以通过document.getElementById(\'myElement\').innerHTML来获取或设置其内容。\n关于HTML属性和JavaScript属性之间的关系，有一些值得注意的点：\n\n有些HTML属性与JavaScript属性有直接对应关系。例如，HTML元素的id属性对应于JavaScript对象的id属性，HTML元素的class属性对应于JavaScript对象的className属性（因为class是JavaScript中的保留字）。然而，并非所有HTML属性都有对应的JavaScript属性，反之亦然。\n当HTML元素被解析为DOM（文档对象模型）时，HTML属性会被转化为DOM属性。然后，可以通过JavaScript访问和修改这些DOM属性。但是，修改DOM属性不一定会改变HTML源代码中的属性。例如，如果你通过JavaScript改变了元素的className，那么在浏览器中查看元素时，你会看到类已经改变，但是如果你查看页面的源代码，原始的class属性仍然保持不变。这是因为源代码只代表了页面最初的状态，而不反映后续通过JavaScript所做的任何更改。\nHTML属性是静态的，它们在页面加载时被设置，并且除非通过JavaScript或用户交互进行更改，否则它们不会改变。另一方面，JavaScript属性是动态的，可以在任何时候通过脚本进行更改。',
        },
        {
            q: 'js中如何判断一个对象是否属于某个类？',
            a: "在JavaScript中，我们通常不使用\"类\"这个概念来描述对象，而是使用原型（prototype）和构造函数（constructor）来定义和识别对象的类型。然而，从ES6开始，JavaScript引入了类的概念，这实际上是基于原型的继承的语法糖。\n\n如果你是在使用ES6类或者想模拟类似的行为，你可以使用instanceof运算符来确定一个对象是否是由特定的类构造的。\n\n下面是一个例子：\n\njavascript\nclass MyClass {  \n  constructor() {  \n    // ...  \n  }  \n}  \n  \nconst obj = new MyClass();  \n  \nif (obj instanceof MyClass) {  \n  console.log('obj is an instance of MyClass');  \n} else {  \n  console.log('obj is not an instance of MyClass');  \n}\n在这个例子中，instanceof运算符会检查obj的原型链中是否存在MyClass.prototype。如果存在，那么obj就被认为是MyClass的一个实例。\n\n然而，需要注意的是instanceof运算符可能会受到原型链更改的影响，而且如果页面上下文被更改（例如，有多个框架或窗口），它可能不会按预期工作。因此，在使用instanceof时应该谨慎。\n\n另外，如果你想检查一个对象是否具有某个特定的属性或方法，而不关心它是否是由某个特定的类构造的，你可以使用in运算符或者hasOwnProperty方法：\n\njavascript\nif ('propertyName' in obj) {  \n  console.log('obj has the property propertyName');  \n}  \n  \nif (obj.hasOwnProperty('propertyName')) {  \n  console.log('obj has its own property propertyName (not inherited)');  \n}\n",
        },
        {
            q: '在 JavaScript中有一个函数，执行直接对象查找时，它始终不会查找原型，这个函数是什么？',
            a: "在JavaScript中，当你想要检查一个对象是否具有某个属性，而不希望查找其原型链时，你可以使用Object.prototype.hasOwnProperty()方法。这个方法返回一个布尔值，表示对象是否具有指定的自有属性（不包括那些从原型链上继承的属性）。\n\n下面是一个使用hasOwnProperty()方法的例子：\n\njavascript\nlet obj = {  \n  prop1: 'value1',  \n  prop2: 'value2'  \n};  \n  \nconsole.log(obj.hasOwnProperty('prop1')); // 输出: true  \nconsole.log(obj.hasOwnProperty('prop2')); // 输出: true  \nconsole.log(obj.hasOwnProperty('prop3')); // 输出: false\n在这个例子中，obj对象有两个自有属性：prop1和prop2。当我们使用hasOwnProperty()方法检查这两个属性时，它返回true。但是，当我们尝试检查一个不存在的属性prop3时，它返回false。\n\n值得注意的是，hasOwnProperty()方法本身是继承自Object.prototype的，但在使用时，它只检查调用它的对象本身的属性，而不包括该对象的原型链上的属性。因此，即使你在一个对象上没有直接定义hasOwnProperty属性，你仍然可以调用它，因为它是从原型链上继承下来的。但是，当你使用hasOwnProperty()方法时，它不会考虑这个继承关系；它只关心对象本身的属性。\n",
        },
        {
            q: '请说几条书写 JavaScript语句的基本规范。',
            a: '在编写 JavaScript 代码时，遵循一定的规范是很重要的，因为它可以帮助你的代码保持清晰、易读，并有助于团队合作。以下是一些基本的 JavaScript 编码规范：\n\n1、使用分号结束语句：虽然在某些情况下，JavaScript 可以自动插入分号，但是明确的在每条语句的末尾使用分号是一个好习惯。这可以防止代码在某些情况下产生歧义。\n2、使用严格的比较操作符：避免使用 == 进行比较，因为它会进行类型转换。相反，应该使用 === 和 !==，它们不会进行类型转换，而是直接比较值和类型。\n3、变量和函数命名：变量和函数名应该使用驼峰命名法（camelCase），例如 myVariable 和 myFunction。常量应该全部大写，用下划线分隔，例如 MY_CONSTANT。\n4、使用花括号包裹代码块：即使代码块只有一行，也应该始终使用花括号 ({}) 包裹它。这有助于防止在添加新行时代码产生错误。\n5、缩进和空格：使用一致的缩进（通常是 2 或 4 个空格），并在操作符和关键字周围使用空格以提高可读性。\n6、注释：为你的代码添加有意义的注释，解释代码的目的和工作方式。但是，避免过度注释，特别是当代码本身已经很清晰时。\n7、避免使用全局变量：尽可能减少全局变量的使用，以防止命名冲突和意外的副作用。使用局部变量或封装在函数或模块中的变量。\n8、错误处理：使用 try...catch 语句来处理可能引发错误的代码段，并提供有意义的错误消息。\n9、代码组织：将相关的代码组织在一起，例如使用函数或模块。这有助于保持代码的模块化和可维护性。\n10、遵循 ECMAScript 标准：尽量遵循最新的 ECMAScript 标准（如 ES6、ES7 等），以利用最新的语言特性和改进。\n此外，还有许多其他的编码规范和最佳实践，可以根据项目需求和团队偏好进行调整。一些团队可能会选择使用自动化工具（如 ESLint）来检查和强制执行这些规范。\n',
        },
        {
            q: '递归优缺点？',
            a: 'javaScript中递归的优点是它可以提供一种简单而优雅的方法来解决复杂的问题，同时使代码更易于理解和维护。另外，使用递归可以避免嵌套过多的循环而导致的代码冗长。\n\n然而，递归也有一些缺点。递归函数经常需要创建新的本地变量和堆栈帧，这可能会占用大量内存并影响性能。递归还可能导致栈溢出错误，特别是在处理大型数据集时。因此，在使用递归时，应该仔细考虑其实现方式以避免这些潜在问题。',
        },
        {
            q: 'js中eval的功能是什么？',
            a: '在JavaScript中，eval()函数是一个全局函数，它可以将传入的字符串作为JavaScript代码进行解析和执行。简单来说，eval()允许你动态地执行JavaScript代码。\n\n例如：\n\njavascript\nlet code = \'console.log("Hello, world!")\';  \neval(code);  // 输出：Hello, world!\n在这个例子中，eval()函数执行了字符串中的JavaScript代码，输出了"Hello, world!"。\n\n然而，尽管eval()功能强大，但在实际开发中，我们通常不推荐使用它，原因主要有以下几点：\n\n安全性问题：eval()会执行任何传入的字符串，这使得它非常容易被恶意利用。如果传入的字符串来自不可信的源（如用户输入），那么它可能会被用来执行恶意代码。\n\n性能问题：eval()在解析和执行代码时可能比直接执行的JavaScript代码慢，因为它需要在运行时解析字符串。\n\n调试困难：使用eval()执行的代码在调试时可能会更困难，因为错误可能会在执行eval()的代码行上报告，而不是在实际出错的代码行上。\n\n因此，尽管eval()在某些情况下可能很有用，但通常最好避免使用它，尤其是在处理来自不可信源的数据时。在许多情况下，都有更安全、更高效的替代方案可以使用。',
        },
        {
            q: 'js ["1", "2", "3"].map(parseInt)的结果？',
            a: '在JavaScript中，当你执行以下代码：\n\njavascript\n["1", "2", "3"].map(parseInt)\n你实际上会得到一个意想不到的结果，这是因为map方法会为回调函数提供三个参数：当前元素值、当前元素的索引和数组本身。而parseInt函数期望接收两个参数：要转换的字符串和一个可选的基数（radix）。\n\n在这种情况下，parseInt会将map提供的索引作为基数（radix）来解释。让我们看看这如何影响每个数组元素：\n\n对于第一个元素"1"，索引是0，所以parseInt("1", 0)会被调用。由于基数为0，parseInt会根据字符串的内容来判断基数。因为字符串是"1"，它会被成功解析为十进制数1。\n对于第二个元素"2"，索引是1，所以parseInt("2", 1)会被调用。但是，基数1是无效的，因此parseInt会返回NaN。\n对于第三个元素"3"，索引是2，所以parseInt("3", 2)会被调用。在二进制中，没有表示3的方式，因此parseInt同样会返回NaN。\n因此，最终的结果数组将是[1, NaN, NaN]，而不是你可能期望的[1, 2, 3]。\n\n要避免这种情况，你应该使用一个自定义的回调函数，确保只传递必要的参数给parseInt，例如：\n\njavascript\n["1", "2", "3"].map(str => parseInt(str, 10));  // [1, 2, 3]\n在这个例子中，箭头函数(str => parseInt(str, 10))确保每个字符串都被解析为十进制数，不考虑它们的索引。',
        },
        {
            q: '在 JavaScript中什么是类（伪）数组？如何将类（伪）数组转化为标准数组？',
            a: '在JavaScript中，类数组（或称为伪数组）对象是一种看起来像数组，但并不是真正的数组的对象。它们通常具有一个 length 属性和可以通过索引访问的元素，但并不具有数组的所有方法。常见的类数组对象包括函数的 arguments 对象、NodeList 对象（由 document.querySelectorAll 或其他DOM查询方法返回）以及由字符串的 split 方法返回的对象（在调用 split 但未将结果赋值给数组时）。\n\n要将类数组对象转换为标准数组，你可以使用 Array.from() 方法或扩展运算符（spread operator）。\n\n使用 Array.from() 方法：\n\njavascript\nlet arrayLike = {0: \'a\', 1: \'b\', 2: \'c\', length: 3};  \nlet realArray = Array.from(arrayLike);  \nconsole.log(realArray); // 输出: ["a", "b", "c"]\n在这个例子中，Array.from() 方法接受一个类数组对象或可迭代对象，并创建一个新的数组实例，其元素是来自该对象的元素。\n\n使用扩展运算符（spread operator）：\n\njavascript\nlet arrayLike = {0: \'a\', 1: \'b\', 2: \'c\', length: 3};  \nlet realArray = [...arrayLike];  \nconsole.log(realArray); // 输出: ["a", "b", "c"]\n扩展运算符可以与任何可迭代对象一起使用，包括类数组对象。在这个例子中，它创建了一个新的数组，并将类数组对象的元素“扩展”到新的数组中。\n\n然而，需要注意的是，对于没有实现迭代器接口的对象（例如，仅仅是具有 length 属性和索引访问的对象），扩展运算符将不会工作。在这种情况下，你需要使用 Array.from() 或其他方法（如 Array.prototype.slice.call()）来转换类数组对象。但是，对于大多数现代用例（如 NodeList 或 arguments 对象），扩展运算符和 Array.from() 应该都能正常工作。\n\n另外，虽然函数的 arguments 对象在ES6之前被广泛认为是类数组对象，但在ES6及以后的版本中，更推荐使用剩余参数（rest parameters）来获取函数参数，这将直接得到一个真正的数组，而无需进行任何转换。例如：\n\njavascript\nfunction myFunction(...args) {  \n  console.log(args); // 总是输出一个数组，无论传入多少参数  \n}\n',
        },
        {
            q: '列出不同浏览器中关于 JavaScript兼容性的常见问题。',
            a: '在Web开发中，确保JavaScript代码在不同浏览器中的兼容性是一个重要的问题。以下是一些关于JavaScript在不同浏览器中兼容性的常见问题：\n\n事件绑定：不同浏览器对事件的处理存在差异，特别是事件绑定的方式。例如，旧版本的Internet Explorer（IE）使用attachEvent方法，而其他浏览器则使用addEventListener。为了解决这个问题，开发者通常需要使用条件判断或者第三方库（如jQuery）来统一事件处理的方式。\nDOM操作：获取和操作DOM元素的方法在不同浏览器中也可能存在差异。例如，旧版本的IE使用document.all和getElementById的方式与其他浏览器略有不同。此外，一些旧的IE版本不支持getElementsByClassName方法。\nCSS样式处理：JavaScript经常用于动态修改CSS样式。然而，不同浏览器对CSS属性的支持程度不同，且有些属性在不同浏览器中的命名也存在差异（如-webkit-、-moz-、-o-、-ms-等前缀）。\nAjax实现：虽然大多数现代浏览器都支持XMLHttpRequest对象用于Ajax通信，但旧版本的IE使用ActiveXObject。因此，在编写Ajax代码时需要考虑到这种差异。\n全局变量和函数：在某些浏览器中（特别是旧版本的IE），无意中创建的全局变量和函数可能会与浏览器内置的对象、函数或变量发生冲突。\nECMAScript 6+ 兼容性：新版本的JavaScript（ECMAScript 6及以后）引入了许多新特性和语法，但并非所有浏览器都完全支持这些新特性。开发者可能需要使用Babel等工具将ES6+代码转换为ES5代码，以确保在更多浏览器中的兼容性。\nHTML5和CSS3特性支持：与JavaScript类似，HTML5和CSS3也引入了许多新特性和API。不同浏览器对这些新特性的支持程度不同，可能需要使用polyfill或第三方库来填补功能缺口。\n浏览器安全策略：不同浏览器有不同的安全策略和限制，如跨域资源共享（CORS）、同源策略（Same-Origin Policy）等。这些策略可能会影响JavaScript代码的执行方式和可访问的资源。\n为了解决这些兼容性问题，开发者通常会采取以下策略：\n\n使用第三方库：如jQuery、Modernizr等，这些库已经处理了许多浏览器之间的差异，使开发者能够编写更简洁、更一致的代码。\n编写条件判断代码：根据浏览器的类型和版本执行不同的代码路径。这通常通过检测浏览器的用户代理字符串（User Agent）来实现。\n使用polyfill：为不支持某些特性的浏览器提供替代实现。例如，如果浏览器不支持Array.prototype.includes方法，可以使用一个polyfill来提供这个功能。\n利用浏览器兼容性表：如Can I use等网站提供了详细的浏览器兼容性信息，帮助开发者了解哪些特性可以在哪些浏览器中使用。\n使用构建工具和转译器：如Babel、Webpack等，这些工具可以将现代JavaScript代码转换为旧版本浏览器可以理解的代码。\n',
        },
        {
            q: 'js中，什么是正负无穷大？',
            a: '在JavaScript中，正负无穷大是特殊的浮点数值，表示一个数超出了可以表示的最大或最小浮点数范围。在JavaScript中，可以使用Infinity和-Infinity来表示正无穷大和负无穷大。\n\n当你执行某些数学运算时，可能会得到这些值。例如，将一个正数除以0会得到Infinity，而将一个负数除以0会得到-Infinity。\n\n以下是一些示例：\n\njavascript\nconsole.log(5 / 0);       // 输出：Infinity  \nconsole.log(-5 / 0);      // 输出：-Infinity  \nconsole.log(Infinity);    // 输出：Infinity  \nconsole.log(-Infinity);   // 输出：-Infinity\n需要注意的是，无穷大并不等于一个具体的数字，而是表示一个趋势或方向。因此，在数学运算中，无穷大可能会产生一些意想不到的结果。例如，无穷大加上任何数都还是无穷大，无穷大减去无穷大则是一个不确定的形式（NaN）。\n\nJavaScript还提供了一些函数和方法来处理无穷大值，例如isFinite()函数可以检查一个数是否是有限的（即不是Infinity、-Infinity或NaN），Number.MAX_VALUE和Number.MIN_VALUE可以分别获取可以表示的最大和最小正浮点数（但并不是无穷大）。然而，对于无穷大的处理需要谨慎，以避免产生错误或不确定的结果。\n',
        },
        {
            q: 'js中什么是全局变量？这些变量如何声明？使用全局变量有哪些问题？',
            a: '在JavaScript中，全局变量是在整个代码范围内都可访问的变量，即在函数内外都可以访问和修改。全局变量在代码的任何位置都可以被读取和修改，这使得它们在整个应用程序中的状态难以追踪和管理。\n\n全局变量可以通过以下几种方式声明：\n\n在函数外部直接声明变量：在JavaScript中，如果一个变量在函数外部声明（即不在任何函数体内），则它默认为全局变量。例如：\n\njavascript\nvar globalVar = "I am global"; // 使用var声明的全局变量  \nlet anotherGlobal = "Another global"; // 使用let声明的全局变量（在ES6及更高版本中）  \nconst constantGlobal = "Constant global"; // 使用const声明的全局常量（在ES6及更高版本中）\n\n请注意，虽然var在旧版本的JavaScript中经常用于声明全局变量，但在现代JavaScript（ES6+）中，更推荐使用let和const来声明变量，因为它们提供了块级作用域，并且可以避免一些var所带来的问题（如变量提升）。\n\n在函数内部但不使用var、let或const关键字：如果在一个函数内部声明变量但没有使用var、let或const，那么这个变量也会成为全局变量。但这种方法不推荐使用，因为它容易引发错误和难以调试的问题。\njavascript\nfunction exampleFunction() {  \n    globalVar = "I am global inside a function"; // 隐式地创建了一个全局变量  \n}  \nexampleFunction();  \nconsole.log(globalVar); // 输出："I am global inside a function"\n\n使用window对象（在浏览器环境中）：在浏览器环境中，全局变量实际上是window对象的属性。因此，通过给window对象添加属性，也可以创建全局变量。\n\njavascript\nwindow.globalVar = "I am global via window object"; // 通过window对象声明全局变量\n\n使用全局变量可能会带来以下问题：\n\n命名冲突：由于全局变量在整个代码范围内都是可见的，不同的代码片段或库可能会尝试使用相同的全局变量名，导致冲突。\n\n难以维护：全局变量可以在代码的任何位置被修改，这使得跟踪变量的状态变得困难，尤其是在大型项目中。\n\n代码耦合：全局变量增加了代码之间的耦合性，因为任何部分的代码都可能依赖于某个全局变量的状态。这使得代码更难以测试、重用和修改。\n\n安全问题：全局变量可能会被恶意代码利用，尤其是在浏览器环境中，可能会导致安全漏洞。\n\n为了避免这些问题，通常建议尽量减少全局变量的使用，并尽可能地将变量限制在局部作用域内。可以使用立即调用的函数表达式（IIFE）来创建局部作用域，或者使用模块模式来封装代码和状态。在ES6及更高版本的JavaScript中，还可以使用模块（通过import和export语句）来更好地管理代码和依赖关系。',
        },
        {
            q: '解释 JavaScript中定时器的工作，并说明使用定时器的缺点。',
            a: '在JavaScript中，定时器允许我们安排代码在未来的某个时间点执行。这主要通过两个函数实现：setTimeout 和 setInterval。\n\nsetTimeout(function, delay): 这个函数会在指定的延迟（以毫秒为单位）后执行一次提供的函数。例如，setTimeout(function() { console.log(\'Hello\'); }, 2000); 会在2秒后打印 "Hello"。\nsetInterval(function, delay): 这个函数会每隔指定的延迟时间重复执行提供的函数。例如，setInterval(function() { console.log(\'Hello\'); }, 2000); 会每隔2秒打印一次 "Hello"。\n然而，使用JavaScript定时器也有一些缺点：\n\n不准确：JavaScript的定时器并不保证完全准确。如果浏览器的标签页被隐藏或者系统资源紧张，定时器可能会被延迟，甚至完全停止。此外，由于JavaScript是单线程的，如果前面的代码执行时间过长，也会影响到定时器的准确性。\n性能问题：大量使用定时器可能会导致性能问题。特别是当使用 setInterval 时，如果函数执行时间过长，可能会导致函数堆积，消耗大量内存和CPU资源。\n难以管理：当在应用中大量使用定时器时，管理和取消它们可能会变得困难。你需要保留每个定时器的ID，以便在需要时取消它们。否则，定时器可能会在你不再需要它们时继续运行，导致不必要的资源消耗。\n可能导致内存泄漏：如果定时器引用的函数或对象在定时器结束前没有被正确清理，那么它们将不会被垃圾回收机制回收，从而导致内存泄漏。\n因此，在使用JavaScript定时器时，我们应该谨慎考虑这些缺点，并尽量使用更现代、更强大的技术，如requestAnimationFrame（用于动画）或Promise和async/await（用于异步操作）等。',
        },
        {
            q: 'js中什么是===运算符？',
            a: '在JavaScript中，=== 是一个"严格相等"运算符。这意味着它不仅比较两个值的内容是否相等，还比较这两个值的类型是否相等。如果内容和类型都相同，那么这两个值就被认为是严格相等的。\n\n这与 ==（"抽象相等"或"宽松相等"）运算符不同，== 会在比较前进行类型转换以尝试使两边的值类型相同。\n\n例如：\n\njavascript\nconsole.log(5 === "5");  // 输出 false，因为虽然内容相同，但一个是数字，一个是字符串  \nconsole.log(5 == "5");   // 输出 true，因为使用 == 时，JavaScript 尝试将字符串 "5" 转换为数字 5  \n  \nconsole.log(null === undefined);  // 输出 false，因为 null 和 undefined 是两种不同的类型  \nconsole.log(null == undefined);   // 输出 true，因为在 == 下，null 和 undefined 被认为是相等的（这是JavaScript的一个特殊规定）\n\n在编程实践中，通常推荐使用 === 来避免由于自动类型转换导致的不预期的行为。',
        },
        {
            q: 'js中void（0）的作用是什么？在什么时候使用？',
            a: '在JavaScript中，void 是一个操作符，用于评估一个表达式并返回undefined。当你看到 void(0) 这样的代码时，它的主要目的是生成一个undefined值，同时确保表达式中的任何副作用都会被执行。\n\n但是，为什么有人会用 void(0) 而不是直接写 undefined 呢？这有几个原因：\n\n历史原因：在早期的JavaScript中，undefined 可以被赋值，这意味着某些代码可能会意外地改变 undefined 的值。虽然现代浏览器已经修复了这个问题，并使 undefined 成为一个只读的全局变量，但 void(0) 仍然被一些开发者用作一种明确的方式来生成一个 undefined 值，而不依赖于全局的 undefined 变量。\n\n代码清晰性：使用 void(0) 可以明确地表示你的意图是生成一个 undefined 值，而不是从某个可能已经被修改的全局变量中获取值。\n\n防止URL跳转：在HTML中，href="javascript:void(0);" 经常被用作一个技巧来防止链接的默认行为（即跳转到另一个页面或重新加载当前页面）。当用户点击这样的链接时，JavaScript代码会执行（在这种情况下是 void(0)），但页面不会跳转或刷新。然而，现代的做法更推荐使用 event.preventDefault() 来达到同样的效果，而不是依赖 javascript:void(0);。\n\n执行副作用：虽然 void(0) 本身没有副作用，但你可以使用 void 操作符来执行其他有副作用的表达式，并确保结果是 undefined。例如，void(someFunctionWithSideEffects()) 会执行函数并忽略其返回值。\n\n总的来说，虽然 void(0) 在某些情况下仍然有用，但现代JavaScript开发通常有更清晰、更可取的方式来达到同样的目的。如果你看到代码中使用了 void(0)，了解它的背景和用途是很重要的。',
        },
        {
            q: '如何强制页面加载 JavaScript中的其他页面？',
            a: '在JavaScript中，强制页面加载其他页面的最常见方法是使用 window.location 对象。你可以设置 window.location.href 为你想要导航到的URL，这将导致浏览器加载并显示该URL的页面。\n\n例如：\n\njavascript\nwindow.location.href = "https://www.example.com";\n\n这行代码将导致浏览器立即导航到 "https://www.example.com" 这个URL。\n\n另外，你还可以使用 window.location.replace() 方法，这个方法与设置 window.location.href 类似，但它不会在历史记录中留下当前页面的记录，因此用户不能使用“后退”按钮返回原页面。\n\n例如：\n\njavascript\nwindow.location.replace("https://www.example.com");\n\n这行代码将导致浏览器导航到 "https://www.example.com" 这个URL，并且当前页面不会被保存在浏览历史中。\n\n请注意，这些方法都会立即导致页面跳转，如果你希望在某个事件（如点击按钮）发生后进行页面跳转，你应该将这些代码放在事件处理函数中。例如：\n\njavascript\ndocument.getElementById("myButton").addEventListener("click", function() {  \n    window.location.href = "https://www.example.com";  \n});\n\n这段代码会在ID为 "myButton" 的元素被点击时，导航到 "https://www.example.com" 这个URL。',
        },
        {
            q: 'JavaScript中不同类型的错误有几种？',
            a: "在JavaScript中，错误主要可以分为以下七种类型：\n\nError：这是最基本的错误类型，通常当运行时错误发生时，会抛出Error类型的错误。你可以使用Error构造函数来创建一个错误对象。\n\njavascript\nthrow new Error('This is a custom error');\n\nRangeError：当一个值不在其所允许的范围内时，会抛出RangeError。例如，将数组的长度设置为一个负数或非数字。\n\njavascript\nlet arr = [];  \narr.length = -1; // RangeError: Invalid array length\n\nReferenceError：当你试图引用一个不存在的变量时，会抛出ReferenceError。\n\njavascript\nconsole.log(undeclaredVariable); // ReferenceError: undeclaredVariable is not defined\n\nSyntaxError：当你试图解析不符合语法规则的代码时，会抛出SyntaxError。\n\njavascript\nlet a = 10 * ; // SyntaxError: Unexpected token '*'\n\nTypeError：当你对一个值进行不适当的操作时，会抛出TypeError。例如，调用一个非函数的对象，或者试图访问null或undefined的属性。\n\njavascript\nlet num = 10;  \nnum(); // TypeError: num is not a function  \n  \nlet obj = null;  \nconsole.log(obj.property); // TypeError: Cannot read property 'property' of null\n\nURIError：当全局URI处理函数被误用时，会抛出URIError。例如，URI的编码或解码函数（encodeURI，decodeURI，encodeURIComponent，decodeURIComponent）如果接收了非法或错误的参数，就会抛出这种错误。\n\njavascript\ndecodeURI('%'); // URIError: URI malformed\n\nEvalError：EvalError表示全局函数eval()的使用方式与其定义的方式不符。然而，在实践中，这种错误类型很少出现，因为在ECMAScript规范中并没有对这种错误类型进行定义。实际上，这种错误类型在ECMAScript规范中已经被废弃了。\n注意：虽然EvalError在ECMAScript规范中仍然存在，但在实际的JavaScript环境中，你可能永远都看不到EvalError。这是因为，按照规范，只有当直接调用eval()函数的方式与JavaScript引擎的内部实现方式不一致时，才会抛出EvalError。然而，在实际情况中，JavaScript引擎的实现方式对于开发者来说是透明的，因此几乎不可能触发EvalError。\n\n除了上述的内置错误类型，开发者还可以定义自己的错误类型，只需继承Error对象即可。例如：\n\njavascript\nclass CustomError extends Error {  \n    constructor(message) {  \n        super(message);  \n        this.name = 'CustomError';  \n    }  \n}  \n  \nthrow new CustomError('This is a custom error');\n",
        },
        {
            q: 'js中forin和forof的区别？',
            a: "在JavaScript中，for...in和for...of都是循环结构，但它们的用途和行为存在一些关键的区别。\n\nfor...in\n\nfor...in语句用于遍历对象的可枚举属性。这里的对象可以是JavaScript中的任何对象，包括数组（尽管通常不建议使用for...in来遍历数组，因为这会遍历所有的可枚举属性，包括原型链上的，而且不保证按照实际数组内部顺序进行遍历）。\n\n例如：\n\njavascript\nlet obj = {a: 1, b: 2, c: 3};  \nfor (let prop in obj) {  \n  console.log('obj.'+ prop + ' = ' + obj[prop]);  \n}\n在这个例子中，for...in循环会遍历对象obj的所有可枚举属性（'a', 'b', 'c'），并打印出它们的属性和值。\n\nfor...of\n\nfor...of语句则用于遍历可迭代对象（包括 Array，Map，Set，String，TypedArray，arguments对象等等）的元素。for...of循环直接提供的是被迭代对象的值，而不是像for...in那样提供的是对象的属性名。\n\n例如：\n\njavascript\nlet arr = ['a', 'b', 'c'];  \nfor (let value of arr) {  \n  console.log(value);  \n}\n在这个例子中，for...of循环会遍历数组arr的所有元素（'a', 'b', 'c'），并打印出它们的值。\n\n总结一下，for...in和for...of的主要区别在于：\n\nfor...in遍历的是对象的属性名（或键名），而for...of遍历的是可迭代对象的元素值。\n\nfor...in不仅会遍历对象自身的属性，还会遍历其原型链上的属性（除非该属性是不可枚举的），而for...of只遍历对象本身的元素。\n\n对于数组，for...in不能保证按照数组的实际内部顺序进行遍历，因为它遍历的是对象的属性名，而属性名的顺序并不一定是按照数值顺序排列的。而for...of则能保证按照数组的实际内部顺序进行遍历。\n\n因此，在遍历数组时，通常建议使用for...of，而在需要遍历对象的属性时，可以使用for...in，但需要注意处理原型链上的属性和属性的枚举性。\n",
        },
        {
            q: '描述一下 JavaScript中的匿名函数。',
            a: "在JavaScript中，匿名函数是没有名称的函数。它们通常用于以下几种情况：\n\n作为回调函数：匿名函数可以作为参数传递给其他函数，并在需要时被调用。例如，在异步编程中，可以使用匿名函数作为回调函数来处理异步操作的结果。\n作为函数表达式：匿名函数可以赋值给变量或作为表达式的一部分。这样可以在代码中创建可重用的函数逻辑，而无需为函数指定一个显式的名称。\n立即调用的函数表达式（IIFE）：匿名函数可以立即被调用，形成一个自执行的函数块。这种模式常用于创建隔离的作用域，避免全局命名空间的污染。\n以下是几个匿名函数的示例：\n\n作为回调函数：\n\njavascript\nsetTimeout(function() {  \n  console.log('这是一个匿名函数作为回调函数的示例');  \n}, 1000);\n在这个例子中，setTimeout 函数接受一个匿名函数作为参数，并在指定的延迟时间后调用它。\n\n作为函数表达式：\n\njavascript\nconst greet = function(name) {  \n  console.log('你好，' + name + '！');  \n};  \n  \ngreet('张三');\n在这个例子中，匿名函数赋值给变量 greet，然后通过 greet('张三') 调用该函数。\n\n立即调用的函数表达式（IIFE）：\n\njavascript\n(function() {  \n  var secret = '这是一个秘密';  \n  console.log(secret);  \n})();  \n  \n// 无法访问到 secret 变量  \n// console.log(secret); // ReferenceError: secret is not defined\n在这个例子中，匿名函数被包裹在圆括号内，并紧接着用另一对圆括号调用它。这样就创建了一个隔离的作用域，secret 变量只在该匿名函数内部可见。\n\n需要注意的是，由于匿名函数没有显式的名称，因此在调试和错误跟踪时可能会比较困难。因此，在某些情况下，给函数命名可能更有帮助，即使你不打算在代码的其他部分直接调用它。现代JavaScript引擎通常支持函数的名称推断，即使函数是匿名的，也可以在堆栈跟踪中显示一个有意义的函数名称。\n",
        },
        {
            q: 'js中什么是构造函数？它与普通函数有什么区别？',
            a: '在JavaScript中，构造函数是一种特殊的函数，它被设计用来初始化一个新创建的对象。构造函数通常以大写字母开头，以区别于普通函数。你可以使用new关键字与构造函数一起创建一个新的对象。\n\n这是一个简单的构造函数的例子：\n\njavascript\nfunction Person(name, age) {  \n    this.name = name;  \n    this.age = age;  \n}  \n  \nvar john = new Person("John", 30);  \nconsole.log(john.name);  // 输出: "John"  \nconsole.log(john.age);   // 输出: 30\n在这个例子中，Person是一个构造函数，它接受两个参数name和age，并将它们分别赋值给新创建对象的name和age属性。\n\n构造函数与普通函数的区别：\n\n调用方式：普通函数可以直接调用，而构造函数需要使用new关键字来调用。\n\n目的：普通函数被设计用来执行特定的任务并返回结果，而构造函数被设计用来初始化新创建的对象。\n\nthis关键字：在普通函数中，this通常指向全局对象（在浏览器中是window），但在构造函数中，this指向新创建的对象。\n\n返回值：普通函数可以返回任何类型的值，但构造函数不需要显式返回任何值。当使用new关键字调用构造函数时，它会自动返回新创建的对象。\n\n命名约定：虽然这不是强制的，但按照惯例，构造函数通常以大写字母开头，而普通函数以小写字母开头。\n\n原型属性：每个构造函数都有一个prototype属性，该属性是一个对象，用于定义所有由该构造函数创建的对象共享的属性和方法。普通函数通常不使用这个属性（尽管它们也有这个属性）。\n\n注意：虽然你可以使用构造函数来创建对象，但在现代JavaScript开发中，更推荐使用对象字面量、Object.create()方法或类（ES6引入）来创建和初始化对象。这是因为这些方法提供了更清晰、更简洁的语法和更好的性能。\n',
        },
        {
            q: '请解释一下 JavaScript和CSS阻塞。',
            a: '当我们谈论网页的加载和渲染时，"阻塞"通常指的是某些资源（如JavaScript和CSS）在加载和执行过程中阻止了HTML页面的进一步解析和渲染。下面我将详细解释JavaScript和CSS的阻塞行为。\n\nJavaScript 阻塞\nJavaScript 是一种解析型语言，浏览器在解析 HTML 文档时，一旦遇到 <script> 标签，就会停下来等待 JavaScript 代码解析和执行完成，然后才能继续解析 HTML。这就是所谓的 JavaScript 阻塞。JavaScript 的这种阻塞行为主要是因为：\n\n解析器阻塞：浏览器不知道接下来的 JavaScript 代码是否会修改当前已经解析的 HTML 内容，或者是否需要使用已经解析的 HTML 元素。因此，浏览器会暂停 HTML 的解析，等待 JavaScript 代码执行完毕。\n\n网络延迟：如果 JavaScript 文件是从网络上加载的，那么网络延迟也会导致 HTML 解析的阻塞。浏览器需要等待 JavaScript 文件完全下载后才能开始执行。\n\n为了减少 JavaScript 阻塞对页面性能的影响，可以采取以下策略：\n\n异步加载：使用 async 属性可以让浏览器异步加载和执行 JavaScript 代码，这样就不会阻塞 HTML 的解析。\n\n延迟加载：使用 defer 属性可以让浏览器延迟执行 JavaScript 代码，直到 HTML 解析完毕。\n\n内联脚本和事件处理器：将脚本直接嵌入 HTML 中或使用事件处理器（如 onclick）可以避免额外的 HTTP 请求，但可能会增加 HTML 文档的大小。\n\n代码拆分和懒加载：将 JavaScript 代码拆分成多个文件，并根据需要懒加载它们。\n\n\nCSS 阻塞\n与 JavaScript 类似，CSS 也会阻塞页面的渲染。当浏览器解析到 HTML 文档中的 <link> 标签引用 CSS 文件时，它会暂停 HTML 的解析，直到 CSS 文件完全下载并应用到页面上。这是因为 CSS 定义了页面的布局和样式，如果缺少 CSS 信息，浏览器就无法正确地渲染页面。\n\n为了减少 CSS 阻塞对页面性能的影响，可以采取以下策略：\n\n内联样式：将样式直接嵌入 HTML 元素中，使用 style 属性。这样可以避免额外的 HTTP 请求，但会增加 HTML 文档的大小，并且不利于样式的复用和维护。\n\n媒体查询：使用媒体查询（Media Queries）可以根据设备的特性（如视口宽度、设备类型等）来应用不同的 CSS 样式。这样可以将不必要的样式延迟到需要时再加载。\n\nCSS 拆分和按需加载：将 CSS 代码拆分成多个文件，并根据页面的需要按需加载它们。这可以通过 JavaScript 动态插入 <link> 标签或使用 CSS-in-JS 库来实现。\n\n优化 CSS 交付：通过压缩、合并和缓存 CSS 文件来减少文件大小和加载时间。还可以使用 CDN（内容分发网络）来加速 CSS 文件的传输。\n\n\n需要注意的是，虽然上述策略可以帮助减少 JavaScript 和 CSS 的阻塞行为，但它们也可能引入其他的问题和复杂性。因此，在实际应用中需要权衡各种因素来选择合适的策略。',
        },
        {
            q: '嵌入的 JavaScript代码应该放在什么位置？',
            a: '嵌入的JavaScript代码可以放在HTML文档的多个位置，但最常见的两个位置是<head>标签内和<body>标签的末尾。\n\n<head>标签内：将JavaScript代码放在<head>标签内意味着在页面内容被加载之前，JavaScript就已经加载并可以运行了。这样做的好处是，如果你的JavaScript代码需要在页面内容加载之前执行某些操作（例如设置全局变量或配置），那么这是一个好位置。然而，如果JavaScript代码依赖于页面的某些元素（例如DOM元素），那么将代码放在<head>中可能会导致错误，因为在代码执行时，这些元素可能还没有被加载。\n\n<body>标签的末尾：将JavaScript代码放在<body>标签的末尾是另一种常见的做法。这样做的好处是，当JavaScript代码执行时，页面的所有内容都已经被加载，因此你可以安全地访问和操作DOM元素。这也是推荐的做法，特别是当你的JavaScript代码与页面内容紧密相关时。\n\n另外，还有一种现代的做法是使用async或defer属性将JavaScript文件异步或延迟加载。这些属性可以放在<script>标签上，用于控制JavaScript文件的加载和执行时间。例如，使用async属性可以让浏览器异步地加载和执行JavaScript文件，而不会阻塞页面的解析。而使用defer属性则可以让浏览器延迟执行JavaScript文件，直到文档的解析完成。这些属性对于提高页面加载性能非常有用。\n\n总的来说，嵌入的JavaScript代码的位置取决于你的具体需求。但是，为了避免阻塞页面加载和提高性能，通常推荐将JavaScript代码放在<body>标签的末尾或使用async或defer属性异步或延迟加载JavaScript文件。',
        },
        {
            q: 'js中 typeof 与 instanceof 的区别是什么？',
            a: '在JavaScript中，typeof和instanceof都是用来检测数据类型的操作符，但它们的用法和目的有所不同。\n\ntypeof：\ntypeof是一个一元操作符，它返回的是表示未经计算的操作数类型的字符串。它主要用于检测基本数据类型（如：number、string、boolean、undefined、symbol（ES6新增）），以及对象、函数和特殊值null。但是，对于除 null 以外的所有对象（包括数组和函数），typeof都会返回object。这是因为在JavaScript中，数组和函数其实都是对象。\n\n例如：\n\njavascript\nconsole.log(typeof 123);        // "number"  \nconsole.log(typeof "123");      // "string"  \nconsole.log(typeof true);       // "boolean"  \nconsole.log(typeof undefined);  // "undefined"  \nconsole.log(typeof Symbol());   // "symbol"  \nconsole.log(typeof null);       // "object"  这是一个历史遗留的错误  \nconsole.log(typeof {});         // "object"  \nconsole.log(typeof []);         // "object"  \nconsole.log(typeof function(){}); // "function"\ninstanceof：\ninstanceof是一个二元操作符，它用于检测构造函数的prototype属性是否出现在对象的原型链中的任何位置。换句话说，它用于检测一个对象是否是由某个构造函数创建的实例。\n\n例如：\n\njavascript\nfunction Car(make, model, year) {  \n    this.make = make;  \n    this.model = model;  \n    this.year = year;  \n}  \n  \nvar auto = new Car(\'Honda\', \'Accord\', 1998);  \n  \nconsole.log(auto instanceof Car); // 输出: true  \nconsole.log(auto instanceof Object); // 输出: true，因为所有的对象都是Object的实例\n\n注意：instanceof不能用来检测基本数据类型，因为它只用于检测对象。对于基本数据类型，instanceof始终返回false。另外，instanceof的结果可能受到页面上下文的影响，因为不同的框架或窗口可能包含不同的构造函数和原型链。因此，在使用instanceof时需要谨慎。\n\n总结一下，typeof和instanceof的主要区别在于：typeof主要用于检测基本数据类型和函数，而instanceof主要用于检测对象是否是由某个特定构造函数创建的实例。',
        },
    ],
    css: [
        {
            q: 'flex布局？',
            a: 'Flex布局是CSS3中新增的一种布局方式，主要用于弹性盒布局，可以便捷地实现各种复杂的布局。它主要包括以下概念：\n\n1. 容器(Container)：采用Flex布局的父元素称为容器，它的display属性设置为flex或inline-flex。\n\n2. 项目(Item)：容器内的直接子元素称为项目，它们可以按照一定的规则在容器内排列。\n\n3. 主轴(Main axis)和交叉轴(Cross axis)：容器的排列方向称为主轴，与之垂直的方向称为交叉轴。\n\n4. 主轴起点和终点：主轴的起点是左端或上端，结束点是右端或下端。\n\n5. 项目的对齐方式：可以指定单个项目在容器主轴的对齐方式，也可以指定所有项目的对齐方式。\n\nFlex布局的常用属性包括：\n\n1. flex-direction：指定主轴的方向，可取值为row（默认值，水平方向）、column（垂直方向）、row-reverse（水平反向）和column-reverse（垂直反向）。\n\n2. justify-content：指定项目在主轴上的对齐方式，可取值为flex-start（默认值，靠近起点对齐）、flex-end（靠近终点对齐）、center（居中对齐）、space-between（两端对齐，项目之间的间隔相等）和space-around（每个项目两侧的间隔相等，项目之间的间隔是它们的一半）。\n\n3. align-items：指定项目在交叉轴上的对齐方式，可取值为flex-start（靠近起点对齐）、flex-end（靠近终点对齐）、center（居中对齐）、baseline（以基线对齐，这里的基线是项目的文字基线）和stretch（默认值，拉伸对齐）。\n\n4. flex-wrap：指定一行放不下时是否换行，默认不换行，可取值为nowrap（默认），wrap（换行），wrap-reverse（反向换行）。\n\n5. align-self：指定单个项目在交叉轴上的对齐方式，可覆盖align-items属性的值。\n\n6. flex：指定项目的放大比例，缩小比例和基准值（默认值为0 1 auto），常用于实现自适应布局和弹性盒子。',
        },
        {
            q: 'CSS三栏布局是一种常用的网页布局方法，实现三栏布局的方法有哪些？',
            a: 'CSS三栏布局是一种常用的网页布局方法，将页面分成左、中、右三列。其中，左列和右列通常用作边栏或导航菜单等，中列则包含主要的内容。实现三栏布局的方法有很多种，以下是其中几种比较常用的方法：\n\n1.浮动布局\n通过float属性，左、右两个栏目往左右浮动，中间栏目使用margin-left和margin-right属性留出空隙。具体实现可以参考以下代码：\nhtml:\n```\n<div class="container">\n  <div class="left">Left column</div>\n  <div class="right">Right column</div>\n  <div class="middle">Middle column</div>\n</div>\n```\n\ncss:\n```\n.container {\n  width: 100%;\n}\n\n.left {\n  float: left;\n  width: 200px;\n}\n\n.right {\n  float: right;\n  width: 200px;\n}\n\n.middle {\n  margin-left: 220px;\n  margin-right: 220px;\n}\n```\n2.绝对定位布局\n左、右两个栏目使用绝对定位，中间栏目使用margin属性留出空隙。具体实现可以参考以下代码：\nhtml:\n```\n<div class="container">\n  <div class="left">Left column</div>\n  <div class="right">Right column</div>\n  <div class="middle">Middle column</div>\n</div>\n```\ncss:\n```\n.container {\n  position: relative;\n}\n\n.left {\n  position: absolute;\n  left: 0;\n  width: 200px;\n}\n\n.right {\n  position: absolute;\n  right: 0;\n  width: 200px;\n}\n\n.middle {\n  margin: 0 220px;\n}\n```\n\n3.flex布局\n\n使用CSS3的flex布局，可以通过flex-wrap属性将三个栏目分别放在一行中。具体实现可以参考以下代码：\n\nhtml:\n```\n<div class="container">\n  <div class="left">Left column</div>\n  <div class="middle">Middle column</div>\n  <div class="right">Right column</div>\n</div>\n```\n\ncss:\n```\n.container {\n  display: flex;\n  flex-wrap: wrap;\n}\n\n.left {\n  width: 200px;\n}\n\n.middle {\n  flex-grow: 1;\n}\n\n.right {\n  width: 200px;\n}\n```\n\n这里的关键是通过flex-grow属性将中间栏目占据剩余空间，从而实现三栏布局。',
        },
        {
            q: '选择器及其优先级？',
            a: 'CSS选择器是一种语法，用于在HTML或XML文档中选择元素样式。常见的选择器包括元素选择器、类选择器、ID选择器、属性选择器等。\n\nCSS优先级是用于决定应用于同一元素的多个样式规则时哪个规则具有较高优先级的算法。优先级值由四个级别的数字计算而得，其中!important声明可以覆盖任何其他规则。\n\n以下是优先级级别（由高到低）：\n\n1. !important声明\n\n2. 内联样式（通过style属性声明的样式）\n\n3. ID选择器\n\n4. 类选择器/属性选择器/伪类选择器\n\n5. 元素选择器/伪元素选择器\n\n如果两个规则的优先级级别相同，则后面定义的规则将覆盖前面的规则。',
        },
        {
            q: 'BFC及其作用场景？',
            a: 'CSS中的BFC（块级格式上下文）是一个独立的渲染区域，它的作用包括：\n\n1. 清除浮动：在父元素设置BFC后，其内部浮动元素不会对外部元素产生影响。\n\n2. 防止垂直外边距重叠：在两个相邻元素都属于同一个BFC时，它们之间的垂直外边距会被折叠，从而避免间距过大或过小的情况出现。\n\n3. 实现多栏布局：使用BFC可以将块级元素分割为多个独立的布局区域。\n\n4. 解决文字环绕问题：当图片等元素与文本混排时，通过将文本放在BFC中可以避免元素对文本的影响，实现文字环绕效果。\n\nBFC的触发方式有很多种，包括float、absolute或fixed定位、display为inline-block、table-cell、flex等。',
        },
        {
            q: '垂直水平居中的几种方式？',
            a: '以下是一些 CSS 实现垂直水平居中的常见方法：\n1. 使用 flexbox 布局，将父元素设置为 display:flex; 并添加 align-items:center; 和 justify-content:center; 属性。\n```css\n.parent {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n```\n\n2. 使用绝对定位和 transform。将子元素设置为 position:absolute; 然后通过 top, bottom, left, right 属性以及 transform 属性来调整其位置。\n```css\n.parent {\n  position: relative;\n}\n\n.child {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n}\n```\n\n3. 使用 table-cell 和 vertical-align。将父元素设置为 display:table-cell; 并添加 vertical-align:middle; 属性，同时将子元素设置为 display:inline-block;。\n```css\n.parent {\n  display: table-cell;\n  vertical-align: middle;\n}\n\n.child {\n  display: inline-block;\n}\n```\n\n4. 使用 grid 布局，将父元素设置为 display:grid; 并添加 justify-items:center; 和 align-items:center; 属性。\n```css\n.parent {\n  display: grid;\n  justify-items:center;\n  align-items:center;\n}\n```\n\n请注意，这些方法可能需要根据具体情况进行微调。',
        },
        {
            q: 'CSS常见布局类型？',
            a: '1. 流动布局（Flow layout）\n\n2. 浮动布局（Float layout）\n\n3. 定位布局（Positioned layout）\n\n4. 弹性盒模型布局（Flexbox layout）\n\n5. 网格布局（Grid layout）\n\n6. 多列布局（Multi-column layout）',
        },
        {
            q: 'CSS哪些属性可以被继承？',
            a: '文字字体：font、font-family、font-size、font-style、font-weight\n\n文字修饰：color、text-indent、text-align、line-height、word-spacing、letter-spacing',
        },
        {
            q: 'CSS预编译器？',
            a: 'CSS预处理器（CSS Preprocessors）是一种工具，它们使用类似于编程语言的语法来帮助简化CSS代码的编写和维护。这些预处理器通常会提供变量、嵌套规则、混合、函数、模块化等功能，可以大大提高CSS开发效率和代码可读性。常见的CSS预处理器有Sass、Less、Stylus等。',
        },
    ],
    html: [
        {
            q: 'HTML标签分类？',
            a: 'HTML 标签可以分为以下几类：\n\n1. 块级元素（Block-Level Elements）：用于构建页面的主要内容，以块的形式展示。常见的块级元素有 <div>、<h1>-<h6>、<p> 等。\n\n2. 行内元素（Inline Elements）：通常用于修饰文本，在行内展示。常见的行内元素有 <span>、<a>、<em>、<strong> 等。\n\n3. 行内块元素（Inline-Block Elements）：类似于行内元素，但可以设置宽度和高度等块级元素特有的属性。常见的行内块元素有 <img>、<input>、<button> 等。\n\n4. 表单元素（Form Elements）：用于创建表单和接收用户输入。常见的表单元素有 <form>、<input>、<select>、<textarea> 等。\n\n5. 特殊元素（Special Elements）：用于在页面中插入特定的内容或功能，如 <meta>、<link>、<style>、<script> 等。',
        },
        {
            q: '浏览器内核？',
            a: '以下是主要的浏览器内核：\n\n1. Chromium/Blink：由Google开发，目前用于Google Chrome、Microsoft Edge等浏览器。\n\n2. Gecko：由Mozilla Foundation开发，被用于Firefox浏览器。\n\n3. WebKit：由苹果公司开发，最初用于Safari浏览器，现在也用于其他浏览器，如Chrome for iOS等。\n\n4. Trident/EdgeHTML：由微软开发，曾经用于Internet Explorer浏览器，现在已被替换为基于Chromium的Microsoft Edge浏览器中的Blink内核。\n\n5. Presto：由挪威公司Opera Software开发，曾经用于Opera浏览器，但自2013年后，Opera也改用Blink作为其浏览器内核。\n\n还有其他一些较小众的浏览器内核，例如Servo、KHTML等。',
        },
        {
            q: '回流与重绘？',
            a: 'HTML 的回流（reflow）和重绘（repaint）是浏览器渲染页面时的两个关键概念。 \n\n回流是指当 DOM 元素的尺寸、位置或内容发生变化时，浏览器需要重新计算它们在渲染树上的位置和样式，然后重新排列页面元素，并且可能触发其他元素的再次回流。 \n\n重绘是指当 DOM 元素的样式发生变化但不影响其布局时，浏览器会将新样式应用于元素并根据最新样式重新绘制元素。这个过程并不会影响元素的位置和大小，也不会触发其他元素的重绘。\n\n因为回流比重绘需要更多的计算资源，所以在编写网页时，应该尽量减少网页的回流次数。可以使用优化技术如避免强制同步的布局属性、尽量利用浏览器的合并操作、使用 flex 布局等方式来减少回流。',
        },
        {
            q: '渐进增强和优雅降级？',
            a: '渐进增强（progressive enhancement）和优雅降级（graceful degradation）是前端开发中两种不同的思路。\n\n渐进增强是指先关注核心功能和最基本的用户体验，然后再逐步地添加更多的功能和交互效果，以确保网站在各种设备和浏览器上都能正常运行并提供最好的用户体验。  \n\n优雅降级则是先构建一个完整的、高度交互的网站，然后再逐步地去掉一些功能和交互效果，以确保网站在某些较低版本的浏览器或设备上也能正常工作。',
        },
        {
            q: 'iframe的优缺点？',
            a: 'iframe（内嵌框架）的优点包括： \n\n1. 可以将其他 HTML 页面或第三方内容嵌入到当前页面，从而扩展网页的功能和内容。\n\n2. 使用 iframe 可以在不刷新整个页面的情况下，动态地加载不同的内容。\n\n3. iframe 具有独立的作用域，可以使每个框架之间的代码和样式相互隔离。\n\n但是，iframe 也有一些缺点：\n1. 如果滥用 iframe，会对页面性能产生负面影响，因为每个 iframe 都需要发送单独的 HTTP 请求进行加载。\n\n2. 使用 iframe 加载第三方内容时，可能会存在跨域安全问题，需要特殊的设置。\n\n3. 搜索引擎会将每个 iframe 视为一个单独的页面，从而影响 SEO 的效果。',
        },
        {
            q: '如何利用浏览器缓存来增加用户体验？',
            a: '浏览器缓存是一种用于加速网站加载速度的技术。这是通过在用户访问网站时将文件（如图片、CSS、JS文件）存储在用户的本地计算机上来实现的。下次用户访问相同的网站时，他们的浏览器可以直接从本地计算机中提供缓存文件而不必重新下载它们，从而加快加载速度并节省带宽。\n\n以下是如何设置和利用浏览器缓存： \n\n1.  服务器响应头部控制：使用浏览器缓存最简单的方法是通过设置响应头部中的 Cache-Control 和 Expires 属性来告诉浏览器何时可以缓存文件以及缓存文件的过期日期。 \n\n2.  版本控制：可以将文件名与其版本号区分开来，以便在更新文件时浏览器可以自动刷新缓存。 \n\n3.  AJAX 控制：通过 AJAX 控制可以在不刷新整个页面的情况下更新其中的一部分，更好的控制何时读取新数据。\n\n如何避免浏览器缓存\n\n有时候我们需要避免浏览器缓存，以便确保浏览器下载最新文件以达到最新状态。以下是几种方法：\n\n1.  URL 参数：通过在 URL 中添加随机参数，可以阻止浏览器从缓存中读取文件，而是强制其重新下载文件。 \n\n2.  更改文件名：更改文件名也可以强制浏览器重新下载文件。\n\n3.  版本号：使用版本号来标识文件，每次更新文件后增加版本号。',
        },
    ],
    // ts: [],
    // nodejs: [],
    vue: [
        {
            q: 'vue和react的区别？',
            a: 'Vue和React都是流行的JavaScript框架，它们有一些区别：\n\n1. 模板语法：Vue使用模板语法，在HTML标签中嵌套JavaScript代码，而React使用JSX语法，将JavaScript代码嵌入到XML-like结构中。\n\n2. 数据绑定：Vue采用双向数据绑定，可以在数据改变时自动更新视图。而React使用单向数据流，将数据从父组件传递到子组件。\n\n3. 状态管理：Vue使用Vuex来管理应用程序状态，而React使用Redux。\n\n4. 组件化：Vue和React都支持组件化开发，但Vue将组件作为一个对象来处理，而React则使用JSX表达式定义组件。\n\n5. 渲染方式：Vue在模板编译时生成渲染函数以提高性能，而React在运行时进行虚拟DOM的重新渲染。\n\n6. 学习曲线：Vue相对于React更易于理解和学习，因为Vue的概念较少且文档也更加清晰易懂。\n\n需要注意的是，这些框架都有其各自的优缺点，根据实际情况选择最适合的工具。',
        },
        {
            q: 'vue的响应式原理？',
            a: 'Vue的响应式原理是通过利用“依赖收集”来实现的。当组件中的数据发生变化时，Vue会自动检测到这些变化并更新其DOM。\n\nvue2 - Object.defineproperty：实例初始化时遍历data里的所有属性，使用defineProperty为其添加getter/setter，\n\n访问对象的属性时getter函数触发，并通过depend()收集依赖，修改属性的值时setter函数触发并通过notify()通知watcher渲染新的页面\n\n缺点：没有对对象新增和删除的数据进行劫持，需要通过$set和delete来增加和删除属性、需要重写数组的变异方法、需要循环递归进行数据拦截绑定\n\nvue3 - proxy（代理）：vue3中使用ref、reactive来创建响应数据，通过使用WeakMap进行依赖收集，ref是reflmpl类的实例，在reflmpl类的get中收集依赖，在set出发依赖。而reactive则通过Proxy实现，用reactive创建数据后返回一个proxy代理对象，之后便可以在该代理对象上进行操作，Proxy在代理对象前架设类一层拦截器来控制对象的访问\n\n特点：不需要对数组方法进行重写、可以检测到属性的增加和删除，缺点是依然需要进行循环递归绑定代理',
        },
        {
            q: 'computed和watch的区别？',
            a: '"computed" 是 Vue.js 中一种属性类型，它根据数据源计算并缓存一个新值用于渲染视图。"computed" 的值通常依赖于其他数据或属性的变化，当这些依赖项发生改变时，"computed" 会自行更新。\n\n"watch" 则是 Vue.js 中另一种属性类型，用于监听数据源的变化并执行指定的回调函数。当数据源发生变化时，"watch" 会执行回调函数，并传入新值和旧值的参数。与"computed" 不同，"watch" 不会返回一个新的值，而是触发一个特定的操作。\n\n简而言之，"computed" 是计算属性，其值依赖于其他属性，而"watch" 是监听属性，其函数在属性变化时被执行。',
        },
        {
            q: 'vfor为什么一定要有key？',
            a: '在使用 Vue 的 v-for 指令对数据进行循环渲染时，每个被渲染的元素都需要提供一个 key 值。这是因为 key 值主要用于 Vue 在执行 DOM Diff 算法时，判断新旧元素是否相同，从而实现最小化地操作 DOM 来更新视图。\n\n如果没有提供 key 值，Vue 会在浏览器控制台输出警告信息，同时会降低应用的渲染性能。\n\n因此，在使用 v-for 指令时一定要为每个被渲染的元素提供一个唯一且稳定的 key 值。可以使用该元素在列表中的索引或者唯一标识符作为 key 值。',
        },
        {
            q: 'vue中组件传值的几种方式？',
            a: 'Vue组件传值的几种方式：\n\n1. Props：父组件通过设置props属性，将数据传递给子组件。\n\n2. Event：子组件通过 `$emit` 方法触发自定义事件，并将数据传递给父组件。\n\n3. Vuex：使用状态管理库Vuex来实现共享数据，每个组件可以访问并更改这些数据。\n\n4. Provide / Inject：父组件通过 `provide` 方法提供数据，任何子孙组件都可以通过 `inject` 方法注入这些数据。\n\n5. Refs：通过在子组件上设置 `ref` 属性，父组件可以访问该组件实例以及其相应的数据。\n\n6. $attrs 和 $listeners：在 Vue 2.x 版本中， `v-bind="$attrs"` 可以将所有父作用域的绑定属性传递给一个子组件。 `v-on="$listeners"` 可以将所有父作用域的监听器传递给一个子组件。',
        },
        {
            q: '首屏加载优化和数据渲染优化策略？',
            a: 'Vue 的首屏加载优化和数据渲染优化策略主要有以下几种：\n\n1. 使用异步组件：将页面上不必要的组件延迟加载，只在需要时才调用，减少首屏加载时间。\n\n2. 压缩代码文件：通过压缩JavaScript、CSS等文件，减小文件体积，从而加快首屏加载速度。\n\n3. 懒加载图片：懒加载可以减少请求次数以及减少页面加载所需要的时间。\n\n4. 使用Vue-loader的ssr优化：使用server side rendering（服务器渲染）技术，在服务端预先渲染页面并返回给用户，减少客户端渲染时间，更快地展示首屏数据。\n\n5. 数据分页渲染：在分页渲染中每次只渲染少量数据，避免一次性渲染大量数据导致卡顿。\n\n6. 合理使用v-if和v-for指令：v-if会在条件满足时渲染元素，v-for支持迭代数组。两种指令结合使用，可以减少不必要的DOM操作，提高性能。\n\n7. 列表数据优化：使用key属性来唯一标识每个被渲染的元素，确保Vue能够高效地监测到列表中每个项的变化。同时，在列表中尽可能使用v-for的方法避免使用列表操作函数（如push和splice），以减少不必要的Dom操作，优化性能。',
        },
        {
            q: 'vue中的虚拟DOM？',
            a: 'Vue中的虚拟DOM是一种内存中的表示方式，它是一个JavaScript对象树，是由解析HTML结构生成的AST抽象语法树，用于描述真实DOM结构。当数据发生变化时，Vue使用虚拟DOM来高效地计算出需要更新的部分，并将其更新到真实DOM上，从而避免了直接操作真实DOM所带来的性能问题。通过比较新旧虚拟DOM树来确定需要更新的部分，Vue可以最小化对真实DOM的操作次数，从而提高应用的性能和响应速度。',
        },
        {
            q: 'vue中的路由守卫？',
            a: 'Vue的路由守卫是一种机制，可以在页面跳转前或跳转后进行拦截、验证和控制。\n\nVue提供了3种不同类型的路由守卫，分别为全局前置守卫、路由独享的守卫和组件内的守卫。\n\n全局前置守卫：通过router.beforeEach注册，在任意路由切换前触发，常用于身份验证、权限控制等。\n\n路由独享的守卫：通过在定义路由时添加beforeEnter属性进行注册。只针对该路由生效，在进入该路由前会被调用。\n\n组件内的守卫：包括beforeRouteEnter、beforeRouteUpdate和beforeRouteLeave三个钩子函数，用于在组件实例内控制路由跳转前后的逻辑。其中beforeRouteEnter不能直接访问组件实例，需要使用next回调获取；\n\n路由守卫的使用场景非常广泛，如登录拦截、鉴权、页面重定向等。比如在用户未登录时拦截进入需要登录权限的页面，或者在某个页面填写了表单但未保存时禁止将页面跳转到其他路径等。',
        },
        {
            q: 'vue2和vue3的区别？',
            a: 'Vue3相对于Vue2的主要区别包括：\n\n1. 性能提升：Vue3引入了响应式系统的重写，使得它比Vue2更快，并引入了Tree-shaking、静态提升等优化。\n\n2. Composition API：Vue3的Composition API允许用户根据逻辑功能而不是组件生命周期来组织代码，提高了代码复用性和可维护性。\n\n3. 改进的TypeScript支持：Vue3中针对TypeScript的支持进行了改进，提供了全面的类型定义，使开发更加便捷。\n\n4. 更好的自定义渲染器：Vue3对自定义渲染器进行了改进，使其更加易于创建自定义渲染器以支持SSR、Canvas、WebGL等领域的应用开发。\n\n此外，Vue3还提出了一些新概念和改动，例如Teleport（原名Portals）、Suspense、全局API的改变等，使开发体验更加优秀。',
        },
        {
            q: 'vue2和vue3如何设置全局属性？',
            a: "在Vue 2中设置全局属性可以使用`Vue.prototype`，例如：\n\n```javascript\n// 设置全局属性\nVue.prototype.$myProperty = 'property value'\n// 在Vue实例中使用它\nthis.$myProperty```\n\n在Vue 3中，可以使用`app.config.globalProperties`来设置全局属性，例如：\n\n```javascript// 创建Vue实例\nconst app = createApp(...)\n// 设置全局属性\napp.config.globalProperties.$myProperty = 'property value'\n// 在Vue实例中使用它\nthis.$myProperty```\n\n需要注意的是，在Vue 3中访问全局属性时不再需要使用`$`前缀。",
        },
    ],
    // react: [],
};
